Monday, May 20, 2019

Publish Subscribe Pattern in LWC (Component Communication).

1. publish-subscribe pattern/model is same like Application Event in Lighting Component. If you want to communicate between components those are not bounding in parent child relationship, but available in same page (say, in same app-builder page), then we will use publish-subscribe pattern. Here Salesforce already publish a pubsub.js file which we will use. You can download pubsub.js file from below link or copy paste below pubsub.js.
https://github.com/trailheadapps/lwc-recipes/blob/master/force-app/main/default/lwc/pubsub/pubsub.js

Now create a LWC component named as pubsub and update the pubsub.js file with downloaded code base or copy paste below source code,

/**
 * A basic pub-sub mechanism for sibling component communication
 *
 * TODO - adopt standard flexipage sibling communication mechanism when it's available.
 */

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
};

2. Now we have to create two more components, one will be used to publish an event and another will be used to subscribe the event. Say, our publisher component name is myPublisher and our subscriber component is mySubscriber.

3.  ************* Publisher Component Details. *************

myPublisher.html
<template>
    <lightning-card title="Publisher Component">
        <lightning-layout>
            <lightning-layout-item>
                <button onclick={displayLabradorDetails}>Labrador</button>
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
</template>

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

export default class MyPublisher extends LightningElement {
    @wire(CurrentPageReference) pageRef;

    displayLabradorDetails(){
        fireEvent(this.pageRef, "eventdetails", "Breed - Labrador");
    }
}

myPublisher.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="myPublisher">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>

    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>   
    </targets>
</LightningComponentBundle>


4.  ************* Subscriber Component Details. *************

mySubscriber.html
<template>
    <lightning-card title="Subscriber Component">
        <lightning-layout>
            <lightning-layout-item>
                {details}
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
</template>

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

export default class MySubscriber extends LightningElement {
    @track details;
    @wire(CurrentPageReference) pageRef;

    connectedCallback() {
        registerListener("eventdetails", this.sutUpDetails, this);
    }
     
    disconnectedCallback() {
        unregisterAllListeners(this);
    }

    sutUpDetails(dogDtl){
        this.details = dogDtl;
    }
}

mySubscriber.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="mySubscriber">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>

    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>   
    </targets>
</LightningComponentBundle>

5. Now authorize your Org and then deploy all above 3 LWC components in your authorized Org and finally drag myPublisher and mySubscriber in your Lightning Page.

6. Output ==>

Initial Load:


















After click on button:

LWC to LWC Communication using Lightning Messaging Service (Part - 2)

In my previous post ( previous post link ) we have learn how to create a Lightning Messaging Service with 6 steps and today we will use the ...