Publish Subscriber model in Lightning Web Component

In this Publish Subscriber model, components are not situated in the same DOM tree or in the same containment hierarchy. If the components want to communicate between themselves, we have to use publish subscriber model in Lightning Web Component, You can compare this model as Application Event in Lightning Aura Component.

Publish Subscriber Model Rules

  • Need to use a singleton library which follows the publish-subscriber pattern. Salesforce has already provided one file called pubsub.js by which we can build the publish subscriber communication.
  • import fireEvent from pubsub.js file in the publisher component. Define the pageReference as currentPageReference and fire or publish the event using this pageReference, event name and event detail in the publisher component.
  • import registerListener, unregisterAllListeners from pubsub.js file in the subscriber component. Then register the event and get the value in the subscriber component.

For example, we have made canvas and palette component. When user select the color in palette, canvas will display the respective color. Obviously, palette is the publisher and canvas is the subscriber. Let’s see:

Step 1: First, we need to copy the pubsub.js file from Salesforce library and create the LWC named “pubsub“.

In the pubsub component we only define the pubsub.js file, which will be used later.

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

Step 2: Then, we need to define palette component named “palettePublisher”.

First, we need to update the palettePublisher.js-meta.xml to set the visibility in Lightning App Builder.

palettePublisher.js-meta.xml

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

Then, we need to write palettePublisher.html file.

palettePublisher.html

<template>
        <lightning-card title="Pallete Publisher">
            <lightning-layout>
                <lightning-layout-item flexibility="auto" padding="around-small">
                    <lightning-combobox label="Color" value={color} options={colorCodeOptions} onchange={changeColor}></lightning-combobox>
                    <div class="slds-p-top_small">
                        <lightning-button label="Change Color!" variant="brand" onclick={handleChangeColor}></lightning-button>
                    </div>                    
                </lightning-layout-item>
            </lightning-layout>          
        </lightning-card>
</template>
      

Through this html, user can select the color from the defined picklist, color.

Then, we need to update palettePublisher.js file.

palettePublisher.js

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

export default class PalletePublisher extends LightningElement {
    @track color;

  @wire(CurrentPageReference) pageRef;

  colorCodeOptions = [
    { label: "green", value: "green" },
    { label: "red", value: "red" },
    { label: "yellow", value: "yellow" },
    { label: "blue", value: "blue" }
  ];

  changeColor(event) {
    this.color = event.target.value;
  }

  handleSearchKeyChange(searchKey) {
    this.searchKey = searchKey;
  }

  handleChangeColor() {
    /*eslint-disable-next-line*/
    console.log("color srs-->" + this.color);
    fireEvent(this.pageRef, "changedColor", this.color);
  }
}

In this js file, we have triggered the event named “changedColor” with color code. Remember, we need to send the page reference attribute as current page reference for successful communication.

Step 3: Need to define the canvas component named “canvasSubscriber“.

First, we need to update the canvasSubscriber.js-meta.xml.

canvasSubscriber.js-meta.xml

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

Then, we need to write canvasSubscriber.html file.

canvasSubscriber.html

<template>
    <lightning-card title="Canvas Subscriber">
        <lightning-layout-item flexibility="grow">
            
<div class="templateBodyClass" style={colorStyle}></div>

        </lightning-layout-item>
    </lightning-card>
</template>      

We are showing the color in canvas based on the selection of color in palette.

Then, we need to write canvasSubscriber.js file.

canvasSubscriber.js

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

export default class CanvasSubscriber extends LightningElement {
    @track color;
  @wire(CurrentPageReference) pageRef;

  connectedCallback() {
    registerListener("changedColor", this.handleChangedColor, this);
  }

  disconnectedCallback() {
    unregisterAllListeners(this);
  }

  handleChangedColor(colorCode) {
    /*eslint-disable-next-line*/
    console.log("color--->" + colorCode);
    this.color = colorCode;
  }

  get colorStyle() {
    return `background-color: ${this.color}`;
  }
}

In this JS file, we have invoked registeredListener() and unregisterAllListeners() in the respective methods such as connectedCallback() and disconnectedCallback().
From the connectedCallback(), we are calling handleChangedColor() where we have set the color and at last we have used getter method of colorStyle property to set the background color.

To make the style for canvas, we have defined canvasSubscriber.css file.

canvasSubscriber.css

.templateBodyClass {
    height: 80px;
}

Result

 

 

 

 

 

 

 

So, whenever we will select the color in Palette, Canvas will show that color in the screen.

27,622 total views, 18 views today

Rating: 4.3/5. From 6 votes.
Please wait...
This entry was posted in Lightning Web Component. Bookmark the permalink.

Leave a Reply