Component Communication & Custom Events

0/4 in this phase0/41 across the roadmap

📖 Concept

Component communication is essential for building modular, reusable LWC components. There are four main patterns:

1. Parent → Child: @api Properties Parent passes data down to child via public properties.

2. Child → Parent: Custom Events Child dispatches events, parent listens with on-prefixed handlers.

3. Unrelated Components: Lightning Message Service (LMS) Publish/subscribe messaging across the DOM — components don't need a parent-child relationship.

4. Sibling Communication:

  • Via shared parent (child A → event → parent → @api → child B)
  • Via LMS (publish/subscribe)
  • Via Platform Events (empApi for real-time)

Custom Events — the standard pattern:

// Child dispatches
this.dispatchEvent(new CustomEvent('itemselected', {
    detail: { id: '001xxx', name: 'Acme' },
    bubbles: false,   // Only reaches immediate parent
    composed: false   // Doesn't cross Shadow DOM boundary
}));

// Parent listens (in template)
// <c-child-component onitemselected={handleSelection}></c-child-component>

Lightning Message Service (LMS): For communication between components that don't share a parent-child relationship. Works across Aura, LWC, and Visualforce.

Event bubbling considerations:

  • bubbles: false (default) — event only reaches the immediate parent
  • bubbles: true — event bubbles up the DOM tree
  • composed: true — event crosses Shadow DOM boundaries
  • Use bubbles/composed cautiously — they create tight coupling

💻 Code Example

codeTap to expand ⛶
1// Component Communication Patterns
2
3// === 1. PARENT TO CHILD — @api Properties ===
4
5// Parent template
6// <c-child-card account-name={selectedName} record-count={count}></c-child-card>
7
8// childCard.js
9import { LightningElement, api } from 'lwc';
10
11export default class ChildCard extends LightningElement {
12 @api accountName; // Set by parent
13 @api recordCount; // Set by parent
14
15 // Public method — parent can call this
16 @api
17 resetState() {
18 this.internalState = 'default';
19 }
20
21 // Note: @api properties are READ-ONLY inside the component
22 // Copy to internal variable if you need to modify
23 internalState = 'default';
24}
25
26// Parent calling child method
27// parentComponent.js
28// const child = this.template.querySelector('c-child-card');
29// child.resetState();
30
31// === 2. CHILD TO PARENT — Custom Events ===
32
33// contactList.js (Child)
34import { LightningElement, api, wire } from 'lwc';
35import getContacts from '@salesforce/apex/ContactController.getContacts';
36
37export default class ContactList extends LightningElement {
38 @api accountId;
39 contacts = [];
40
41 @wire(getContacts, { accountId: '$accountId' })
42 wiredContacts({ data, error }) {
43 if (data) this.contacts = data;
44 }
45
46 handleSelect(event) {
47 const contactId = event.currentTarget.dataset.id;
48 const contact = this.contacts.find(c => c.Id === contactId);
49
50 // Dispatch custom event to parent
51 this.dispatchEvent(new CustomEvent('contactselected', {
52 detail: {
53 contactId: contact.Id,
54 contactName: contact.FirstName + ' ' + contact.LastName,
55 email: contact.Email
56 }
57 }));
58 }
59}
60
61// accountPage.js (Parent)
62// <c-contact-list
63// account-id={recordId}
64// oncontactselected={handleContactSelected}>
65// </c-contact-list>
66
67// handleContactSelected(event) {
68// const { contactId, contactName, email } = event.detail;
69// this.selectedContact = { contactId, contactName, email };
70// }
71
72// === 3. UNRELATED COMPONENTS — Lightning Message Service ===
73
74// First, create a Message Channel (metadata XML)
75// messageChannels/Record_Selected.messageChannel-meta.xml
76// <?xml version="1.0" encoding="UTF-8"?>
77// <LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
78// <masterLabel>Record Selected</masterLabel>
79// <isExposed>true</isExposed>
80// <lightningMessageFields>
81// <fieldName>recordId</fieldName>
82// <description>The selected record ID</description>
83// </lightningMessageFields>
84// <lightningMessageFields>
85// <fieldName>recordName</fieldName>
86// </lightningMessageFields>
87// </LightningMessageChannel>
88
89// Publisher component
90import { LightningElement, wire } from 'lwc';
91import { publish, MessageContext } from 'lightning/messageService';
92import RECORD_SELECTED from '@salesforce/messageChannel/Record_Selected__c';
93
94export default class RecordPublisher extends LightningElement {
95 @wire(MessageContext)
96 messageContext;
97
98 handleRecordClick(event) {
99 const payload = {
100 recordId: event.currentTarget.dataset.id,
101 recordName: event.currentTarget.dataset.name
102 };
103 publish(this.messageContext, RECORD_SELECTED, payload);
104 }
105}
106
107// Subscriber component (can be ANYWHERE on the page)
108import { LightningElement, wire } from 'lwc';
109import { subscribe, unsubscribe, MessageContext } from 'lightning/messageService';
110import RECORD_SELECTED from '@salesforce/messageChannel/Record_Selected__c';
111
112export default class RecordSubscriber extends LightningElement {
113 selectedRecordId;
114 selectedRecordName;
115 subscription = null;
116
117 @wire(MessageContext)
118 messageContext;
119
120 connectedCallback() {
121 this.subscription = subscribe(
122 this.messageContext,
123 RECORD_SELECTED,
124 (message) => this.handleMessage(message)
125 );
126 }
127
128 disconnectedCallback() {
129 unsubscribe(this.subscription);
130 this.subscription = null;
131 }
132
133 handleMessage(message) {
134 this.selectedRecordId = message.recordId;
135 this.selectedRecordName = message.recordName;
136 }
137}
138
139// === 4. REAL-TIME EVENTS — empApi (Platform Events in LWC) ===
140import { LightningElement } from 'lwc';
141import { subscribe as empSubscribe, unsubscribe as empUnsubscribe,
142 onError } from 'lightning/empApi';
143
144export default class RealTimeNotifications extends LightningElement {
145 channelName = '/event/Order_Event__e';
146 subscription = {};
147
148 connectedCallback() {
149 this.handleSubscribe();
150 this.registerErrorListener();
151 }
152
153 handleSubscribe() {
154 empSubscribe(this.channelName, -1, (response) => {
155 // Handle incoming Platform Event
156 const eventData = response.data.payload;
157 console.log('New order event:', eventData);
158 // Update UI, show notification, etc.
159 }).then(sub => {
160 this.subscription = sub;
161 });
162 }
163
164 registerErrorListener() {
165 onError((error) => {
166 console.error('EMP API error:', error);
167 });
168 }
169
170 disconnectedCallback() {
171 empUnsubscribe(this.subscription);
172 }
173}

🏋️ Practice Exercise

Component Communication Practice:

  1. Build a parent-child component pair using @api properties and custom events
  2. Create a sibling communication pattern through a shared parent
  3. Implement Lightning Message Service for two unrelated components on the same page
  4. Build a real-time notification component using empApi (Platform Events in LWC)
  5. Create a master-detail UI: selecting an account shows contacts, selecting a contact shows details
  6. Implement a pub/sub pattern for 3 components that react to the same event
  7. Build a component that exposes a public @api method for the parent to call
  8. Create a search component that publishes results via LMS to a map/chart component
  9. Handle complex event data (arrays of objects) in custom events
  10. Write Jest tests for component communication (mock events, verify handlers)

⚠️ Common Mistakes

  • Using bubbles:true and composed:true without understanding the implications — events crossing Shadow DOM boundaries create tight coupling and make debugging harder

  • Not unsubscribing from LMS or empApi in disconnectedCallback — causes memory leaks and ghost event handlers

  • Passing mutable objects in event.detail — the receiver can modify the original data. Use Object.freeze() or spread for immutability

  • Using querySelector to communicate between components — this is fragile. Use @api properties, events, or LMS instead

  • Forgetting that LMS requires a Message Channel metadata XML file — it must be deployed to the org before the components can use it

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Component Communication & Custom Events. Login to unlock this feature.