Communication between independent lightning web components - pubsub

You can use pubsub library or lightning message service to communicate between components that are not in same DOM hierarchy. These methods can be used to communicate between sibling components in an outer lwc component or between to lwc components that are dropped separately in lightning app builder.

Here we will go through an example of lwcComponentA that displays three car company names using radio button. When a car company is selected in this component we will fire a carselected event from lwcComponentA with the name selected by the user. A separate lwcComponentB, that is outside direct DOM hierarchy of lwcComponentA, will listen to this event and display the selected name in lwcComponentB.

Example

1) Create a utility lwc component with name pubsub. Copy paste below code into it's JavaScript file. Please note that this is a utility component provided by Salesforce. But current version available in the link is doing pageRef check and it is not working as expected. So use below version shared by another Salesforce team member instead.

/**
* A basic pub-sub mechanism for sibling component communication
*
* TODO - adopt standard flexipage sibling communication mechanism when it's available.
*/
const events = {};
/**
* 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) => {
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 => {
try {
listener.callback.call(listener.thisArg, payload);
} catch (error) {
// fail silently
}
});
}
};
export {
registerListener,
unregisterListener,
unregisterAllListeners,
fireEvent
};
view raw pubsub.js hosted with ❤ by GitHub

2) Create a lwc component with name lwcComponentA

.green-border {
border: 3px solid green;
padding: 50px;
width: 250px;
height: 250px;
float:left;
}
<template>
<div class="green-border">
<h1>Component A (Publisher)</h1>
<br/>
<lightning-radio-group
name="cars" label="Cars" options={cars} value={selectedValue} type="radio" onchange={sendDataToComponentB}>
</lightning-radio-group>
</div>
</template>
import { LightningElement, wire } from 'lwc';
import { fireEvent } from 'c/pubsub';
import { CurrentPageReference } from 'lightning/navigation';
export default class LwcComponentA extends LightningElement {
@wire(CurrentPageReference) pageRef;
cars = [
{ label: "Audi", value: "Audi" },
{ label: "Tesla", value: "Tesla" },
{ label: "BMW", value: "BMW" },
];
selectedValue = '';
sendDataToComponentB(event) {
fireEvent(this.pageRef ,'carselected', event.target.value);
}
}

3) Create a lwc component with name lwcComponentB

.red-border {
border: 3px solid red;
padding: 50px;
width: 250px;
height: 250px;
float:left;
margin-left: 10px;
}
<template>
<div class="red-border">
<h1>Component B (Subscriber)</h1>
<br/>
Value obtained from component A
<br/>
<b>{valueGotFromA}</b>
</div>
</template>
import { LightningElement } from 'lwc';
import { registerListener, unregisterAllListeners } from 'c/pubsub';
export default class LwcComponentB extends LightningElement {
valueGotFromA = '';
connectedCallback(){
registerListener('carselected', this.diplaySelectedCar, this);
}
disconnectedCallback() {
unregisterAllListeners(this);
}
diplaySelectedCar(carName) {
this.valueGotFromA = carName;
}
}

3) Create a parent lwc component with name parentLwc that can contain both components

<template>
<div class="parent">
<c-lwc-component-a></c-lwc-component-a>
<c-lwc-component-b></c-lwc-component-b>
</div>
</template>
view raw parentLwc.html hosted with ❤ by GitHub

3) Now if you surface the parent component in lightning experience, it will look like

child to parent lwc communication

Explanation

In lwcComponentA component, when value of car is changed in the radio button, it calls JavaScript method in controller sendDataTolwcComponentB. This function uses fireEvent method in pubsub library and fires a custom event with name carselected. Selected carname is also passed as data in the event payload.

lwcComponentB component, has a listener added for carselected event. It is achieved by using registerListener method in pubsub utility. Inside connected callback we add a listener using the line registerListener('carselected', this.diplaySelectedCar, this);. This basically tells "whenever a carselected event is received, call diplaySelectedCar method in JavaScript controller.

Finally when diplaySelectedCar method is called, we take value from the event payload and assigns to valueGotFromA variable in JavaScript

Things to Note

  • This is a common pattern to pass values between independent lwc components in same page.
  • Make sure that you are using the pubsub library shared above. Otherwise you might get an error pubsub listeners need a "@wire(CurrentPageReference) pageRef" property.

Communicating from child lightning web component to parent lightning web component

You need to fire CustomEvent from child component to communicate to a parent component.

Here we will go through an example of a child component that displays three car company names as a radio button. When a car company is selected in the child component we will fire a carchange event from child component with the name selected by the user. Parent component will listen to this event and display the selected name in parent component.

Example

1) Create an lwc component with name childLwc

.child-container {
border: 3px solid red;
margin:50px 10px;
padding: 0 20px;
}
view raw childLwc.css hosted with ❤ by GitHub
<template>
<div class="child-container">
<h1>Child Component</h1>
<lightning-radio-group
name="cars" label="Cars" options={cars} value={selectedValue} type="radio" onchange={sendDataToParent}>
</lightning-radio-group>
</div>
</template>
view raw childLwc.html hosted with ❤ by GitHub
import { LightningElement } from "lwc";
export default class ChildLwc extends LightningElement {
cars = [
{ label: "Audi", value: "Audi" },
{ label: "Tesla", value: "Tesla" },
{ label: "BMW", value: "BMW" },
];
selectedValue = '';
sendDataToParent(event) {
console.log(event.target.value);
this.dispatchEvent(new CustomEvent('carchange', { detail: event.target.value }));
}
}
view raw childLwc.js hosted with ❤ by GitHub

2) Create an lwc component with name parentLwc

.parent {
border: 3px solid green;
padding: 50px;
}
view raw parentLwc.css hosted with ❤ by GitHub
<template>
<h1>Parent Component</h1>
<div class="parent">
Value displayed in parent component => {valueSentByChild}
<c-child-lwc oncarchange={displayValueInParent}></c-child-lwc>
</div>
</template>
view raw parentLwc.html hosted with ❤ by GitHub
import { LightningElement } from 'lwc';
export default class ParentLwc extends LightningElement {
valueSentByChild = '';
displayValueInParent(event) {
this.valueSentByChild = event.detail;
}
}
view raw parentLwc.js hosted with ❤ by GitHub

3) Now if you surface the parent component in lightning experience, it will look like

child to parent lwc communication

Explanation

In childLwc component, when value of car is changed in the radio button, it calls JavaScript method in controller sendDataToParent. This function then dispatches/fires a custom event with name carchange. For every custom event, if you are passing data, you need to pass it as a JSON object with detail attribute.

parentLwc component, has a listener added for carchange event. It is achieved by adding on+eventname attribute while refering child component in html. In our case it is oncarchange attribute added at line 5 of parentLwc component html. This basically tells "whenever a carchage event is received, call displayValueInParent method in JavaScript controller.

Finally when displayValueInParent method is called, we take value from the event.details and assigns to valueSentByChild variable in JavaScript

Things to Note

  • This is a very common pattern to pass values from child component to parent component
  • Make sure that you are using detail attribute while sending data
  • Make sure that your event name is all small letters. ie carchange instead of carChange
  • Make sure that your listener name is all small letters. ie oncarchange instead of oncarChange

Communicating from parent lightning web component to child lightning web component

It is easy to communicate/send data from a parent lightning web component to a child lwc component. You can define a variable in the child lwc component javascript file, annotated it with @api annotation and finally pass value from parent component to the child component by using this variable name.

Example

1) Create an lwc component with name childLWCComponent

<template>
Value received from parent lwc component to child lwc component ==> <b>{childVariable}</b>
</template>
import { LightningElement, api } from 'lwc';
export default class ChildLWCComponent extends LightningElement {
@api childVariable;
}

2) Create an lwc component with name parentLWCComponent

<template>
<!-- Notice that the uppercase characters in child component name needs to be replaced with - character -->
<c-child-l-w-c-component child-variable="Value sent from Parent"></c-child-l-w-c-component>
<c-child-l-w-c-component child-variable={dynamicVariable}></c-child-l-w-c-component>
</template>
import { LightningElement } from 'lwc';
export default class ParentLWCComponent extends LightningElement {
dynamicVariable = 'Dynamic value from Parent';
}

If you surface the parent component in lightning experience, it will looks like

parent to child lwc communication

Explanation

Here childLWCComponent has a javascript variable childVariable annotated with @api annotation. Parent component can pass value using this attribute name. But please note that camel case variable names in child component is converted to kebab case in parent component.

Use Cases

This is the most common form of communication/exchange of data between lwc components. Usually complex lightning apps are built as a combination of multiple child lwc components. ie a parent lwc component contining multiple child lwc components. This parent component will retain data that is needed by all child components and sends it to child components using variables defined in child component using @api annotation.


How to display a modal popup using aura components

When you build custom interfaces it is very common need to show a modal window or a popup window. In this example we will go through how we can build a reusable modal window using lightning aura components. If you are looking for a lightning web component (lwc) version of modal/popup component please check out lwc modal.

Use cases

  • Display modal window or popup window in user interface
  • Show Yes/No confirmation modal popups

Steps to Implement

STEP 1 :- Create a component event with name Util_PressEvent. This event is used to track click event on buttons that can be used to add listeners on modal component. Find the event file and metadata below,

<aura:event type="COMPONENT" description="Generic component event for all press actions" />
<?xml version="1.0" encoding="UTF-8"?>
<AuraDefinitionBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>41.0</apiVersion>
<description>This event is used to represent click/press events.</description>
</AuraDefinitionBundle>

STEP 2 :- Create an aura component with name Util_ModalWindow. This component will contain all code needed to show and hide the modal popup,

<aura:component >
<aura:attribute name="title" type="string" description="Title of the popup" />
<aura:attribute name="cancelLabel" type="string" default="" description="Label for cancel button" />
<aura:attribute name="confirmLabel" type="string" default="Okay" description="Label for OK button" />
<aura:attribute name="isHidden" type="Boolean" default="true" description="Flag used to toggle popup" />
<aura:registerEvent name="onSaveClick" type="c:Util_PressEvent"/>
<aura:registerEvent name="onCancelClick" type="c:Util_PressEvent"/>
<aura:method name="changeVisibility" action="{!c.changeVisibility}" description="show/hide this component">
<aura:attribute name="isShow" type="Boolean" default="false" />
</aura:method>
<div>
<div aria-hidden="{!v.isHidden}" role="dialog"
class="{!v.isHidden ? 'slds-modal slds-modal__close slds-fade-in-close' : 'slds-modal slds-modal__open slds-fade-in-open'}">
<div class="slds-modal__container">
<div class="slds-modal__header">
<h2 class="slds-text-heading--medium">
{!v.title}
</h2>
<button class="slds-button slds-modal__close slds-button--icon-inverse" title="Close" onclick="{!c.defaultCloseAction}">
<!-- <c:svg svgPath="/resource/SLDS214/assets/icons/utility-sprite/svg/symbols.svg#close" category="standard" size="large" name="close" /> -->
X
</button>
</div>
<div class="slds-modal__content slds-p-around--medium">
<div>
{!v.body}
</div>
</div>
<div class="slds-modal__footer">
<aura:if isTrue="{! !empty(v.cancelLabel)}">
<button class="slds-button slds-button--neutral" onclick="{!c.fireCancelEvent}">{!v.cancelLabel}</button>
</aura:if>
<button class="slds-button slds-button--brand" onclick="{!c.fireSaveEvent}">{!v.confirmLabel}</button>
</div>
</div>
</div>
<div class="{!v.isHidden ? 'slds-backdrop slds-backdrop--close' : 'slds-backdrop slds-backdrop--open'}" aura:id="backdrop" ></div>
</div>
</aura:component>
.THIS {
}
@media screen and (max-width: 900px) and (min-width: 300px){
.THIS.cLIModalWindow .slds-modal__footer .slds-button {
min-width: 0;
margin: .20em;
padding: .20em;
}
.THIS.cLIModalWindow .slds-modal__footer {
padding: .75rem 0;
text-align: right;
}
}
({
defaultCloseAction : function(component, event, helper) {
component.set("v.isHidden", true);
},
fireSaveEvent : function(component, event, helper) {
var event = component.getEvent("onSaveClick");
event.fire();
},
fireCancelEvent : function(component, event, helper) {
var event = component.getEvent("onCancelClick");
event.fire();
},
changeVisibility: function(component, event, helper) {
var isShow = event.getParam('arguments').isShow;
console.log("isShow ==>",isShow);
component.set("v.isHidden", !isShow);
}
})

STEP 3 :- You are all set to use the modal component. Let us use the modal component inside an aura component to show a modal popup. Here we are using a parent aura component with name DemoAuraComponent to show modal popup.

<aura:component implements="force:appHostable">
<aura:attribute name="isModalHidden" type="Boolean" default="true" description="Flag to control modal window" />
<lightning:card title="Modal Demo">
<p class="slds-p-horizontal_small">
This is a parent component from where we need to show the modal window
</p>
<lightning:button variant="brand" label="Show Modal" title="Show Modal" onclick="{! c.openModal }" />
</lightning:card>
<c:Util_ModalWindow isHidden="{!v.isModalHidden}" cancelLabel="cancel" onSaveClick="{! c.onConfirm }" onCancelClick="{! c.onCancel }">
This is the modal body. Other Aura/LWC components can be embedded here.
</c:Util_ModalWindow>
</aura:component>
({
openModal : function(component, event, helper) {
component.set("v.isModalHidden", false);
},
onConfirm: function(component, event, helper) {
console.log('Will be called when confirm button is clicked on modal');
component.set("v.isModalHidden", true);
},
onCancel: function(component, event, helper) {
console.log('Will be called when cancel button is clicked on modal');
component.set("v.isModalHidden", true);
}
})

Explanation

Here we can add any content including other aura components between opening and closing tags of Util_ModalWindow. All the content between the tags will be rendered as the body of the modal window.

Example

  • Before opening Modal

  • After opening modal


How to send notifications to bell icon in Salesforce

Salesforce allows us to send custom notifications to bell icon in lightning experience using "custom notification" feature. These custom notifications can be sent using process builders or flows or REST API.

Unfortunately there is no native method in apex that you can just call to send notification yet. One work around is to make a a callout to Salesforce rest endpoint from Apex. In this blog we will go through examples of sending custom notifications through different channels.

Use cases

  • Can be used to send notifications to desktop/lightning experience bell icon or to Salesforce mobile app as push notification
  • Any scenario of when you need to get user's attention in Salesforce. For example, notify sales agent about an activity on an opportunity they are working on, notify administrator about failures in a critical API call etc

Using apex to send notification


public class NotificationManager {
public static Id notificationId = [Select Id, CustomNotifTypeName from CustomNotificationType WHERE CustomNotifTypeName = 'Error Notification' LIMIT 1].Id;
//Method to send notifications
public static void sendNotification(List<Id> receipients, String title, String body, Id targetId) {
Http http = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(Url.getOrgDomainUrl().toExternalForm() + '/services/data/v48.0/actions/standard/customNotificationAction');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
req.setHeader('Content-Type', 'application/json');
//Constructing payload
Map<String, Object> notificationObjMap = new Map<String, Object>();
notificationObjMap.put('title', title);
notificationObjMap.put('body', body);
notificationObjMap.put('customNotifTypeId', notificationId);
notificationObjMap.put('targetId', targetId);
notificationObjMap.put('recipientIds', receipients);
Map<String, Object> payload = new Map<String, Object>{'inputs' => new List<Object>{notificationObjMap}};
req.setBody(JSON.serialize(payload));
HttpResponse res = http.send(req);
System.debug(res.getBody());
}
}

Explanation

Salesforce still hasn't made the ability to send custom notifications from apex available in apex. So we need to call Salesforce REST API (Custom Notification Actions) from apex to to trigger the notifications.

In this example we are expecting a custom notification to be created from setup with name Error Notification. Once you have a the custom notification setup, you can just call the method using below syntax.


List<Id> recipientList = new List<Id>{Userinfo.getUserId()}
String title = 'Please check the opportunity';
String body = 'Important change in opportuntiy';
Opporutnity opp = [SELECT Id FROM Opportunity LIMIT 1];
NotificationManager.sendNotification(recipientList, title, body, opp.Id);

Steps to send notification through configuration

  1. Create a custom notification from setup


  2. Create a process builder entry to fire on updates of opportunity records


  3. Example. Update any opportunity and see result in bell icon