PubSub Model
🔄 Communicating Between Unrelated Components in LWC Using the PubSub Model
In Salesforce Lightning Web Components (LWC), direct communication between unrelated components—those that don't share a parent-child relationship—cannot be accomplished using standard property or event passing. To overcome this limitation, the Publish-Subscribe (PubSub) Model offers a flexible solution for decoupled communication, enabling a clean, event-driven architecture across the Lightning Component DOM.
🎯 What is the PubSub Model?
The PubSub Model in LWC creates a communication channel for sibling or unrelated components rendered on the same page. It allows one component (the publisher) to fire an event and another component (the subscriber) to listen for and handle that event—without a direct hierarchical or structural dependency.
⚠️ Important Note: PubSub communication works only among components on the same page (within the same
Lightning App PageorRecord Page).
🧭 Overview of PubSub Communication Flow
A shared utility component named
pubsubmanages event listeners and dispatchers.The Publisher Component captures input and fires an event using
fireEvent().The Subscriber Component registers for that event using
registerListener()and updates its state based on the data received.
🧱 Step-by-Step Implementation
1. 📡 pubsub Utility Module (The Event Channel)
This module facilitates registering, firing, and unregistering custom events among unrelated LWC components.
✅ Tip: Create a Lightning Web Component named
pubsuband delete its HTML file, retaining only the.jsand.js-meta.xmlfiles.
📁 pubsub.js
/**
* A reusable PubSub implementation for sibling/unrelated LWC components.
*/
const eventRegistry = {};
// Compares two page references to ensure same context
const isSamePage = (refA, refB) => {
const a = refA.attributes;
const b = refB.attributes;
return Object.keys({ ...a, ...b }).every((key) => a[key] === b[key]);
};
const registerListener = (eventName, callback, context) => {
if (!context.pageRef) {
throw new Error('PubSub requires a @wire(CurrentPageReference) pageRef');
}
if (!eventRegistry[eventName]) {
eventRegistry[eventName] = [];
}
const alreadyRegistered = eventRegistry[eventName].some(
listener => listener.callback === callback && listener.context === context
);
if (!alreadyRegistered) {
eventRegistry[eventName].push({ callback, context });
}
};
const unregisterListener = (eventName, callback, context) => {
if (eventRegistry[eventName]) {
eventRegistry[eventName] = eventRegistry[eventName].filter(
listener => listener.callback !== callback || listener.context !== context
);
}
};
const unregisterAllListeners = (context) => {
Object.keys(eventRegistry).forEach(eventName => {
eventRegistry[eventName] = eventRegistry[eventName].filter(
listener => listener.context !== context
);
});
};
const fireEvent = (pageRef, eventName, data) => {
const listeners = eventRegistry[eventName];
if (listeners) {
listeners.forEach(listener => {
if (isSamePage(pageRef, listener.context.pageRef)) {
try {
listener.callback.call(listener.context, data);
} catch (error) {
console.error(`Error delivering pubsub event: ${error}`);
}
}
});
}
};
export {
registerListener,
unregisterListener,
unregisterAllListeners,
fireEvent
};
2. 🧾 Publisher Component: eventBroadcaster
This component accepts user input and broadcasts it to other components using fireEvent().
🔧 eventBroadcaster.html
<template>
<lightning-card title="Publisher Component">
<div class="slds-p-around_medium">
<lightning-input
type="text"
label="Enter Text to Share"
onchange={captureInput}>
</lightning-input>
<lightning-button
variant="brand"
label="Publish Message"
onclick={dispatchEventToSubscribers}>
</lightning-button>
</div>
</lightning-card>
</template>
⚙️ eventBroadcaster.js
import { LightningElement, wire } from 'lwc';
import { fireEvent } from 'c/pubsub';
import { CurrentPageReference } from 'lightning/navigation';
export default class EventBroadcaster extends LightningElement {
userInput;
@wire(CurrentPageReference) pageRef;
captureInput(event) {
this.userInput = event.target.value;
}
dispatchEventToSubscribers() {
fireEvent(this.pageRef, 'dataTransferEvent', this.userInput);
}
}
3. 📬 Subscriber Component: eventReceiver
This component listens for the custom event and updates its display when new data is received.
🔧 eventReceiver.html
<template>
<lightning-card title="Subscriber Component">
<div class="slds-p-around_medium">
<p><strong>Received Data:</strong> {sharedData}</p>
</div>
</lightning-card>
</template>
⚙️ eventReceiver.js
import { LightningElement, wire } from 'lwc';
import { CurrentPageReference } from 'lightning/navigation';
import { registerListener, unregisterAllListeners } from 'c/pubsub';
export default class EventReceiver extends LightningElement {
sharedData;
@wire(CurrentPageReference) pageRef;
connectedCallback() {
registerListener('dataTransferEvent', this.updateData, this);
}
updateData(payload) {
this.sharedData = payload;
}
disconnectedCallback() {
unregisterAllListeners(this);
}
}
🔍 Key Concepts Recap
| Concept | Description |
fireEvent() | Method in Publisher to broadcast data. |
registerListener() | Method in Subscriber to listen for event. |
pageRef | Ensures components are on the same page. |
pubsub.js | Acts as a central messaging utility. |
✅ Benefits of PubSub in LWC
Loose coupling between components.
Reusability across different contexts.
Seamless data flow in app-level designs.
Avoids unnecessary re-renders or controller-level logic.
📌 Final Notes
Always ensure both components share the same
pageRef, as this is crucial for the event to be received.Unregister listeners in
disconnectedCallbackto avoid memory leaks or stale event handling.PubSub is ideal for communication between sibling or unrelated components on the same Lightning page, but not across different pages or apps.
💡 Use Case Ideas
A global notification bell triggering updates in multiple widgets.
A search filter input updating multiple result panes.
Real-time updates across dashboard widgets.

