Lightning Web Components: Use of Pubsub in LWC

Scenarios where fired event is required to handle on the same application page however in different components which aren't directly/indirectly related?

Like in aura we have application event, LWC is enriched with Pubsub module. All we just need to import Pubsub module and use its listener methods.


Component Bundles:
  1. furnitureOnRentDemo (Parent Comp )
  2. furnitureDemo (Child Comp)
  3. pubsubDemo (Comp not in any relation but on same page)
  4. pubsub (Salesforce provided module)
** components are created as a part of proof of concept. it can be modified and optimized as per requirement.**

References:

  • Manish Choudhary LWC learning videos [Available in Udemy]


Snippet:


Code:

furnitureOnRentDemo.html

<template>
    <lightning-card title="Available Furnitures">
        <lightning-layout>
            <lightning-layout-item size="4" padding="around-small">
                <ul>
                    <template for:each={furnitureinfo} for:item="furniture">
                        <li key={furniture.name} style="padding: 10px">
                            <c-furniture-demo furnitureinfo={furniture} ></c-furniture-demo>
                        </li>
                    </template>
                </ul>
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
</template>

-------------------------------------------------------------------------------------------------------------------------

furnitureOnRentDemo.js

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

export default class FurnitureOnRentDemo extends LightningElement {
    furnitureinfo=[
        {name:'Crystal chairs',type:'chair'},
        {name:'Poly tables',type:'table'},
        {name:'Wooden furnitures',type:'sofa'},
        {name:'Iron furnitures',type:'bed'}
    ];
}

-------------------------------------------------------------------------------------------------------------------------

furnitureDemo.html

<template>
    <div class="slds-p-around_medium lgc-bg" onclick={tileClickHandler}>
        <lightning-tile label={furnitureinfo.name} >
            <p class="slds-truncate" title={furnitureinfo.type}>Furniture Type: {furnitureinfo.type}</p>
        </lightning-tile>
    </div>
</template>

-------------------------------------------------------------------------------------------------------------------------

furnitureDemo.js

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

export default class FurnitureDemo extends LightningElement {
    @api furnitureinfo; 

    @wire(CurrentPageReference) pageReference;

    tileClickHandler(){

        fireEvent(this.pageReference, 'selectedtile', this.furnitureinfo);
    }
}

-------------------------------------------------------------------------------------------------------------------------

pubsubDemo.html

<template>
    <lightning-card title="Selected Furniture">
        You have seleteced : {selectedTile.name}
    </br>
        type : {selectedTile.type}
    </lightning-card>
</template>

-------------------------------------------------------------------------------------------------------------------------

pubsubDemo.js

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

export default class PubsubDemo extends LightningElement {
    @track selectedTile={};
    @wire(CurrentPageReference) pageRef;

    connectedCallback(){
        registerListener('selectedtile', this.furnitureSelectHandler, this);
    }
    
    disconnectedCallback(){
        unregisterAllListeners(this);
    }

    furnitureSelectHandler(payload){
        this.selectedTile = payload;
    }
}

-------------------------------------------------------------------------------------------------------------------------

pubsub.js

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

const events = {};

/**
 * Confirm that two page references have the same attributes
 * @param {object} pageRef1 - The first page reference
 * @param {object} pageRef2 - The second page reference
 */
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
};





No comments:

Post a Comment