Component communication and State
Learning Outcome Guide
Section titled “Learning Outcome Guide”At the end of this class, you should be able to…
- Explain how data may be passed into or read from a web component using attributes and properties.
- Emit custom events from a web component for listening.
- Expose methods and properties for external JavaScript control.
- Store and update simple internal component state and synchronize it with properties/attributes.
Coding Demo
Section titled “Coding Demo”In-Class Demo Jan 2026 term
To run the coding demo, you need to have your Student Workbook open in Visual Studio Code.
-
Open the terminal window and paste in the following.
Run from the root of your repository pnpm dlx tiged --disable-cache --force DG-InClass/SDEV-1150-A04-Jan-2026/sk/lesson-21 ./src/lesson-21 -
Walk through the steps in the
ReadMe.mdof the new lesson.
Instructor Demo
Section titled “Instructor Demo”Add Button to Trigger Custom Event
Section titled “Add Button to Trigger Custom Event”In user.card.js, update the template markup by adding a Follow <button>:
...<slot name="name" class="name"></slot><slot name="description" class="description"></slot><button>Follow</button>...This button will be used to dispatch a custom event when clicked with user id in event.detail.
Add Property to Custom Component
Section titled “Add Property to Custom Component”To track the current state of being “followed”, add a private property to the UserCard:
...constructor() { super();
// Added property to track follow state this._followed = false; ...Now, we need to add support for toggling the followed state for the component.
...constructor() { ...}
_setFollow(value) { this._followed = value;}...The last thing to do is connect the button to the followed state. Add an event listener to the button in the constructor:
...constructor() { super(); this._followed = false; const shadow = this.attachShadow({ mode: 'open' }); const content = template.content.cloneNode(true); const img = content.querySelector('img'); img.src = this.getAttribute('avatar') || 'https://placehold.co/80x80/0077ff/ffffff';
this._btn = content.querySelector('button'); this._btn.addEventListener('click', () => { this._setFollow(!this._followed); console.log('Follow button clicked'); });
shadow.appendChild(content);}...Improve the UX
Section titled “Improve the UX”Update the example so that the state change is reflected in the UI. Add a private method for updating the state (_onFollow()) and update the text in the Follow button:
...constructor() { super(); this._followed = false; const shadow = this.attachShadow({ mode: 'open' }); const content = template.content.cloneNode(true); const img = content.querySelector('img'); img.src = this.getAttribute('avatar') || 'https://placehold.co/80x80/0077ff/ffffff'; this._btn = content.querySelector('button');
this._btn.addEventListener('click', () => this._onFollow());
shadow.appendChild(content);}
_setFollow(value) { this._followed = value; this._btn.textContent = this._followed ? 'Following' : 'Follow'; }
// Follow button handler_onFollow() { this._setFollow(!this._followed);}Enable Programmatic Access
Section titled “Enable Programmatic Access”Expose follow() and unfollow() methods that toggles an internal follow state. This will allow external APIs to interact with the component programmatically.
...constructor() { ...}
follow() { this._setFollow(true);}unfollow() { this._setFollow(false);}...We can now set the followed state from our JS. Select the first user-card and follow() and unfollow() it programmatically:
...// Programmatically follow the first user carddocument.querySelector('user-card').follow();When the page loads, you should now see that the first <user-card> is followed (e.g., the button text shows “Following”).
Open the console and enter the following to see the effect of unfollowing a user:
document.querySelector('user-card').unfollow();Dispatch a Custom Event and Property for External Communication
Section titled “Dispatch a Custom Event and Property for External Communication”Finally, let’s dispatch a custom event (follow-change) in case any listeners want to be notified when a <user-card> is followed/unfollowed. Add a read-only property to read the state of _followed as well:
...constructor() { ...}..._setFollow(value) { this._followed = value; this._btn.textContent = this._followed ? 'Following' : 'Follow'; // emit event so parent can react this.dispatchEvent(new CustomEvent('follow-change', { detail: { id: this.getAttribute('user-id') || null, followed: this._followed }, bubbles: true, composed: true }));}
// Property to read followed stateget followed() { return this._followed;}...Listen for and respond to the event in the main application:
document.querySelector('user-card').addEventListener('follow-change', (e) => { const card = e.target; const name = card.querySelector('[slot=name]').textContent; console.log(`User ${name} is ${card.followed? 'followed' : 'not followed'}`);});Student Exercise
Section titled “Student Exercise”Styling Update
Section titled “Styling Update”Add styling for the added Follow button.
Toggle the Details
Section titled “Toggle the Details”If you have completed the previous student task of adding a “Details” section to the card, add the ability to toggle the visiblity of the “Details” within the component (e.g., CSS display update). Trigger an event whenever the details display is toggled.
Push to Your GitHub Workbook Repo
Section titled “Push to Your GitHub Workbook Repo”Once you’re done making your own custom updates to the project, stage your files, commit your work, and push to the remote repository.
- Open a terminal in VS Code
- Stage all updated and created files:
git add .- Commit the changes:
git commit -m 'Lesson 21 Example'- Push your changes to the remote workbook repository:
git push origin mainCommon Errors & Fixes
Section titled “Common Errors & Fixes”| Issue | Cause | Solution |
|---|---|---|
| Parent doesn’t receive event | CustomEvent not bubbled/composed | Use { bubbles: true, composed: true } when dispatching |
| Property updates not reflected | Using attributes only or missing setter | Implement a setter that updates internal state and the rendered UI |
| Can’t call method on element | Query happened before element parsed/connected | Query after DOMContentLoaded or place code after import that defines the custom element |