Publish Subscribe Model (PubSub model) in LWC

In this post, we will use the Publish-Subscribe Model in LWC (also known as pubsub model in LWC) to make communication between two independent components. We can communicate between components which share the same hierarchy in the following ways:

  1. Parent to Child communication which you can check in detail here.
  2. Child to Parent communication which you can check in detail here.

Publish-Subscribe (pubsub) Model in LWC is used to make communication between two independent components. Please note that the Publish-Subscribe Model only works for components that are on the same page.

First, we will implement the Pubsub model in LWC. Then, we will see the better alternative for the Pubsub model in LWC.

Steps to Implement

  1. Create a pubsub component and put the Publish-Subscribe singleton library code provided by Salesforce in JS Controller.
  2. Create a publishCmp component that will fire the event and import CurrentPageReference from lightning/navigation module and use @wire decorator to store the reference of current page.
  3. Import fireEvent from Publish-Subscribe module and use fireEvent() to fire the event.
  4. Create a subscribeCmp component that will subscribe to the event and import registerListener and unregisterListener from Publish-Subscribe library to register and un-register listener respectively. We can use unregisterAllListeners to unregister all the listeners.
  5. Use @wire decorator to store the reference of the current page.
  6. Register listener in connectedcallback() and un-register listener in disconnectedCallback().

Implementation

In this implementation, we will create a publishCmp component, that will have a text box and a button. After clicking a button, we will send a text from publishCmp component and display it in a subscribeCmp component.

First, create a component pubsub to include a Publish-Subscribe module to handle events. This library contains all the methods to fire, listen, and un-register the event.

pubsub component will have following JS code in controller and default html code in html file.

pubsub.html

<template>
    
</template>

pubsub.js

Note: If there are any ‘&amp;&amp;’ in the below code, replace it with ‘&&’.

/**
 * A simple publish-subscribe utility for Sibling component communication.
 */

const events = {};

const samePageRef = (pageRef1, pageRef2) => {
    const obj1 = pageRef1.attributes;
    const obj2 = pageRef2.attributes;
    return Object.keys(obj1)
        .concat(Object.keys(obj2))
        .every(key => {
            return obj1[key] === obj2[key];
        });
};

/**
 * Registers a callback for an event
 * @param {string} eventName - Name of the event to listen for.
 * @param {function} callback - Function to invoke when said event is fired.
 * @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
 */
const registerListener = (eventName, callback, thisArg) => {
    // Checking that the listener has a pageRef property. We rely on that property for filtering purpose in fireEvent()
    if (!thisArg.pageRef) {
        throw new Error(
            'pubsub listeners need a "@wire(CurrentPageReference) pageRef" property'
        );
    }

    if (!events[eventName]) {
        events[eventName] = [];
    }
    const duplicate = events[eventName].find(listener => {
        return listener.callback === callback && listener.thisArg === thisArg;
    });
    if (!duplicate) {
        events[eventName].push({ callback, thisArg });
    }
};

/**
 * Unregisters a callback for an event
 * @param {string} eventName - Name of the event to unregister from.
 * @param {function} callback - Function to unregister.
 * @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
 */
const unregisterListener = (eventName, callback, thisArg) => {
    if (events[eventName]) {
        events[eventName] = events[eventName].filter(
            listener =>
                listener.callback !== callback || listener.thisArg !== thisArg
        );
    }
};

/**
 * Unregisters all event listeners bound to an object.
 * @param {object} thisArg - All the callbacks bound to this object will be removed.
 */
const unregisterAllListeners = thisArg => {
    Object.keys(events).forEach(eventName => {
        events[eventName] = events[eventName].filter(
            listener => listener.thisArg !== thisArg
        );
    });
};

/**
 * Fires an event to listeners.
 * @param {object} pageRef - Reference of the page that represents the event scope.
 * @param {string} eventName - Name of the event to fire.
 * @param {*} payload - Payload of the event to fire.
 */
const fireEvent = (pageRef, eventName, payload) => {
    if (events[eventName]) {
        const listeners = events[eventName];
        listeners.forEach(listener => {
            if (samePageRef(pageRef, listener.thisArg.pageRef)) {
                try {
                    listener.callback.call(listener.thisArg, payload);
                } catch (error) {
                    // fail silently
                }
            }
        });
    }
};

export {
    registerListener,
    unregisterListener,
    unregisterAllListeners,
    fireEvent
};

Create a publishCmp component with a text box and a button. Use onclick and onchange event on button and text box respectively to get the text value in strText in the JS controller once the button is clicked.

publishCmp.html

<template>
    <lightning-card title="Publish Component">

        <lightning-input class="slds-p-around_medium" label="Enter Text to Send: " value={strText} 
            onchange={changeName}></lightning-input>
        <br/>
        <lightning-button class="slds-p-around_medium" label="Publish" variant="brand" 
            onclick={publishEvent}></lightning-button>
        
    </lightning-card>
</template>

In the JS controller of this component, we have to import the necessary modules mentioned in Steps to Implement. Once the publish button is clicked, publishEvent() will be called that will fire the event. We need to pass page reference, event name, and payload to fireEvent().

publishCmp.js

import { LightningElement, wire } from 'lwc';
import { fireEvent } from 'c/pubsub';
import { CurrentPageReference } from 'lightning/navigation';

export default class PublishCmp extends LightningElement {
    strText = '';
    @wire(CurrentPageReference) objpageReference;

    changeName(event){
        this.strText = event.target.value;
    }
    
    // This method will fire the event and pass strText as a payload.
    publishEvent(){
        fireEvent(this.objpageReference, 'sendNameEvent', this.strText);
    }
}

subscribeCmp has simple code to display the received text.

subscribeCmp.html

<template>
         
    <lightning-card  title="Subscribe Component">
        <p class="slds-p-around_medium" style="font-size:25px">
            Received Text: {strCapturedText}
        </p>
    </lightning-card>

</template>

In JS controller, we have to import modules mentioned in Steps to Implement. We should register the listener in connectedCallback(). This method executes when the component is rendered on DOM. We have to pass event name, callback method, and “this” reference to registerListener().

Callback method setCaptureText() will run once the event is captured and assign the payload value to strCapturedText. The same will be reflected in UI.

We will un-register the listener in disconnectedCallback() as it will run once the component is removed from the DOM. As we have only one event in this demonstration, I am using unregisterAllListeners(this) to un-register the events.

subscribeCmp.js

import { LightningElement, wire } from 'lwc';
import { CurrentPageReference } from 'lightning/navigation';
import { registerListener, unregisterAllListeners } from 'c/pubsub';

export default class SubscribeCmp extends LightningElement {
    strCapturedText = '';
    @wire(CurrentPageReference) pageRef;

    // This method will run once the component is rendered on DOM and will add the listener.
    connectedCallback(){
        registerListener('sendNameEvent', this.setCaptureText, this);
    }

    // This method will run once the component is removed from DOM.
    disconnectedCallback(){
        unregisterAllListeners(this);
    }

    // This method will update the value once event is captured.
    setCaptureText(objPayload){
        this.strCapturedText = objPayload;
    }

}

Publish Subscribe Model in LWC

After adding both components on the same page, this is how our implementation will look:

Publish Subscribe Model in LWC
Publish Subscribe Model in LWC

This is how we can use the Publish-Subscribe (pubsub) Model in LWC to communicate between independent events.

If you want to know more about this, you can check the official Salesforce developer guide here.

Now, regarding the better alternative to Pubub model in LWC, it is the new feature that is introduced in the Winter ’20 release which is Lightning Message Service (LMS) – Future of Communication. Click the link to check the complete implementation.

If you don’t want to miss new posts, please Subscribe here. Thanks !

Leave a Comment