Background
Lightning Message Service (LMS) was introduced by Salesforce in the Winter '20 release as a developer preview (available in developer edition orgs but not production or sandbox orgs). It will benefit Visualforce, Aura and LWC developers by providing a unified messaging layer for communicating between Visualforce and Lightning Components.
It provides a simple API to publish messages throughout Lightning Experience and subscribe to messages that originated within the same lightning experience browser tab. It is a front-end service and does not induce ajax server calls even for communication between Visualforce and Lightning Components.
One of the use cases it solves is the ability for between LWC components not in the same DOM tree. Previous to LMS if we needed to send an event (communication) to another LWC that wasn't a parent of the source component, we needed to use a singleton library that followed the publish-subscribe pattern. Available in the lwc-recipes repo. Link here. Below is a working use case of Lightning Message Service between LWC not part of the same DOM tree.
Lightning Messaging Service LWC Example (Github)
Demo
Code Overview
For the example use case, I took inspiration from the Develop an Account Geolocation App with Aura Components trailhead project. The use case is simple in which we have three components one for account search that publishes a message to a message channel when accounts are found, and an account list and account map component that subscribe to the message channel for updates and render the account information and account locations on the map.
Account Locator Message Channel
Message channels are defined by an xml file and placed in the main/default/messageChannels folder of the force-app directory of the project. They define the metadata associated with a specific channel. Below is the definition of the lightning message channel for the account locator app. It has a single lightning message field that will be used to store the accounts retrieved by the account search component.
<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<masterLabel>AccountLocatorMsgChannel</masterLabel>
<isExposed>true</isExposed>
<description>Message channel to pass accounts in the Account Locator.</description>
<lightningMessageFields>
<fieldName>accounts</fieldName>
<description>Accounts that are loaded.</description>
</lightningMessageFields>
</LightningMessageChannel>
Account Search Component Publish
The markup and configuration of the account search component (LwcAccountSearch) can be found in the github repository here. But the publishing to the messaging channel happens in the javascript file. As you can see there is a couple of additional imports to get relevant lightning message service methods and also import the message channel where the message will be published.
import { LightningElement, track } from 'lwc';
import searchAccounts from '@salesforce/apex/AccountSearchController.searchAccounts';
import { publish,createMessageContext,releaseMessageContext } from 'lightning/messageService';
import ACCOUNTLOCATORMC from '@salesforce/messageChannel/AccountLocatorMsgChannel__c';
/** The delay used when debouncing event handlers before invoking Apex. */
const DELAY = 300;
export default class LwcAccountSearch extends LightningElement {
@track searchTerm = '';
//Lightning Message service
context = createMessageContext();
connectedCallback() {
searchAccounts({searchTerm: this.searchTerm})
.then(result => {
const message = {
accounts: result
};
publish(this.context, ACCOUNTLOCATORMC, message);
}).catch(error => {
console.log('error: ' + JSON.stringify(error));
});
}
onSearchTermChange(event) {
// Debouncing this method: Do not update the reactive property as long as this function is
// being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
window.clearTimeout(this.delayTimeout);
const searchKey = event.target.value;
this.delayTimeout = setTimeout(() => {
this.searchTerm = searchKey;
searchAccounts({searchTerm: this.searchTerm})
.then(result => {
const message = {
accounts: result
};
publish(this.context, ACCOUNTLOCATORMC, message);
}).catch(error => {
console.log('error: ' + JSON.stringify(error));
});
}, DELAY);
}
disconnectedCallback() {
releaseMessageContext(this.context);
}
}
Code Highlights
context = createMessageContext();
Creates the context to send the message on. (This browser window)
const message = {
accounts: result
};
publish(this.context, ACCOUNTLOCATORMC, message);
Creates the message and populates the lightning message fields. and publishes the message to the created context. Context preserve the message from being consumed by a component in another browser session.
releaseMessageContext(this.context);
Releases the context when the component is unloaded from the DOM.
Account List and Map Component Subscribe
The markup and configuration file for the list and map component can be found in the GitHub repo here. But subscribe and subsequent action happens in the javascript files below. The actions the two components take when the message is received are different but the mechanism for subscription and unloading is identical.
LwcAccountList.js
import { LightningElement, track } from 'lwc';
import {subscribe,unsubscribe,createMessageContext,releaseMessageContext} from 'lightning/messageService';
import ACCOUNTLOCATORMC from '@salesforce/messageChannel/AccountLocatorMsgChannel__c';
export default class LwcAccountList extends LightningElement {
context = createMessageContext();
subscription = null;
@track cols;
@track rows;
constructor() {
super();
if(this.subscription) {
return;
}
this.subscription = subscribe(
this.context,
ACCOUNTLOCATORMC, (message) => {
this.onAccountsLoaded(message);
})
}
onAccountsLoaded(message) {
this.cols = [
{
'label': 'Name',
'fieldName': 'Name',
'type': 'text'
},
{
'label': 'Phone',
'fieldName': 'Phone',
'type': 'phone'
},
{
'label': 'Website',
'fieldName': 'Website',
'type': 'url'
},
{
'label': 'Action',
'type': 'button',
'typeAttributes': {
'label': 'View details',
'name': 'view_details'
}
}
];
this.rows = message.accounts;
}
disconnectedCallback() {
unsubscribe(this.subscription);
this.subscription = null;
releaseMessageContext(this.context);
}
}
LwcAccountMap.js
import { LightningElement, track } from 'lwc';
import {subscribe,unsubscribe,createMessageContext,releaseMessageContext} from 'lightning/messageService';
import ACCOUNTLOCATORMC from '@salesforce/messageChannel/AccountLocatorMsgChannel__c';
export default class LwcAccountMap extends LightningElement {
context = createMessageContext();
subscription = null;
@track mapMarkers = [];
constructor() {
super();
if(this.subscription) {
return;
}
this.subscription = subscribe(
this.context,
ACCOUNTLOCATORMC, (message) => {
this.onAccountsLoaded(message);
})
}
onAccountsLoaded(message) {
let markers = [];
for ( let i = 0; i < message.accounts.length; i++ ) {
let account = message.accounts[i];
let marker = {
'location': {
'Street': account.BillingStreet,
'City': account.BillingCity,
'PostalCode': account.BillingPostalCode
},
'title': account.Name,
'description': (
'Phone: ' + account.Phone +
'<br/>' +
'Website: ' + account.Website
),
'icon': 'standard:location'
};
markers.push( marker );
}
this.mapMarkers = markers;
}
disconnectedCallback() {
unsubscribe(this.subscription);
this.subscription = null;
releaseMessageContext(this.context);
}
}
Code Highlights
import {subscribe,unsubscribe,createMessageContext,releaseMessageContext} from 'lightning/messageService';
import ACCOUNTLOCATORMC from '@salesforce/messageChannel/AccountLocatorMsgChannel__c';
Instead of the publish the subscribe and unsubscribe methods are imported from the lightning message service.
context = createMessageContext();
subscription = null;
Similar to below the context is initialized so the correct message channel is subscribed to and the subscription is initialized.
if(this.subscription) {
return;
}
this.subscription = subscribe(
this.context,
ACCOUNTLOCATORMC, (message) => {
this.onAccountsLoaded(message);
});
Account Locator Message channel is subscribed to within the initialized context.
unsubscribe(this.subscription);
this.subscription = null;
releaseMessageContext(this.context);
When the component is unloaded subscription is retired and the context is release.
Conclusion
Hopefully this blog gives you insight on using the Lightning Message Service for future inter-component communication requirements. It is important to note that this service is still in developer preview, but it is safe to assume that it will soon be generally available.
Comments