Pages

Thursday, February 25, 2021

Lightning Message Service: Communication between Lightning Web Components

 

Motivation behind this


Earlier I have written a post on Drag and Drop functionality using Salesforce Lightning Web Components leveraging pub-sub event propagation which works on publish-subscribe model. 

With the introduction of Lightning Message Service (LMS) the communication between components can be possible either in single page or multiple pages over a message channel. It also works to communicate among Visualforce pages, Aura components, Lightning Web Components.

This flexible event communication mechanism drives me to do a proof-of-concept.

Lets get started.


Use Case


Business has a requirement to see a Company's location on google map when company is chosen.

Developer wants to build using Lightning Web Components where one component will list down the companies and other component will show the map. Both are unrelated but listen to the events based on subscription.

Possible End Results


After building the use case, it will perform the functionality as following video:


The screen will look like this:

LMS Demo



Solution Approach


To start with, first create a message channel and push it to the org. the xml will be placed under force-app/main/default/messageChannels/ directory.

Here message channel name has given as Selected_EntityId and the file name will be Selected_EntityId.messageChannel-meta.xml

For more information, refer Create a Message Channel

<?xml version="1.0" encoding="UTF-8" ?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>SelectedEntityId</masterLabel>
    <isExposed>true</isExposed>
    <description>Message Channel to pass a entity Id</description>
    <lightningMessageFields>
        <fieldName>entityInfo</fieldName>
        <description>This is the record information that selected</description>
    </lightningMessageFields>
</LightningMessageChannel>


Lets create publisher component, here it is showAccountList which will display accounts and clicking on the Account it will publish events.

showAccountList component:

HTML will like below, here onclick event has been used with div.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<template> 
    <lightning-card title="Lightning Message Service: communication between LWC Components" 
                icon-name="custom:custom30">
    <div class="c-container">  
        <lightning-layout>
            <lightning-layout-item padding="around-small">   
                <template if:true={accounts.data}>                
                    <template for:each={accounts.data} for:item="account">
                        <div class="column" key={account.Id}
                            data-item={account.Id} onclick={handleClick}>
                            <div class="slds-text-heading_small">{account.Name}</div>
                        </div>
                    </template>
                </template>
            </lightning-layout-item>
        </lightning-layout>
    </div>
    </lightning-card>
</template> 

showAccountList.js

Few notable points in this js file:
  • Importing message channel to communicate via LMS.

// Import message service features required for publishing and the message channel
import { publish, MessageContext } from 'lightning/messageService';
import selectedEntity from '@salesforce/messageChannel/Selected_EntityId__c';
  • Retrieving Account information from Controller importing getAccountLocations
  • Publishing a message where entire account information is collected as an array and sending as payload in handleClick() event.
Entire js file as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* eslint-disable no-console */
import { LightningElement, wire} from 'lwc';
import getAccountLocations from '@salesforce/apex/AccountHelper.getAccountLocations';

// Import message service features required for publishing and the message channel
import { publish, MessageContext } from 'lightning/messageService';
import selectedEntity from '@salesforce/messageChannel/Selected_EntityId__c';

export default class ShowAccountList extends LightningElement {
    
    @wire(MessageContext)
    messageContext;
    
    //retrieve account records from database via Apex class
    @wire(getAccountLocations) accounts;

    //clicking the div fires this event
    handleClick(event) {
        //retrieve AccountId from div
        let accountId = event.target.dataset.item;
        let arr = this.accounts.data.find(element => element.Id == accountId);
        let payload = { record: arr };
        publish(this.messageContext, selectedEntity, payload);       
    }       
}

AccountHelper.cls

getAccountLocations method it fetches Account data based on SOQL query. I want to show specific records for demo, that's why filter condition has been added.

public with sharing class AccountHelper {
    @AuraEnabled (cacheable=true)
    public static List<Account> getAccountLocations(){
        return[SELECT Id, Name, BillingStreet, BillingCity, 
    BillingState, BillingPostalCode, BillingCountry 
                FROM Account
                WHERE isDisplay__c = true];
    }
}

Screen will look like this:

showAccountList

Let's move to subscriber component which subscribes for the event and display account location in the map.

displayLocationSubscriber component

It will be like this:

displayAccountSubscriber

Code as follows where lightning-map is used to display location.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<template>
    <lightning-card title="Lightning Map component will show selected Company Location" 
        icon-name="custom:custom30">
        <template if:true={isDisplayLocation}>
            <div class="slds-text-heading_small">{selectedAccountName}</div>                         
            <lightning-map
                map-markers={mapMarkers}
                markers-title={selectedAccountName}
                zoom-level = {zoomLevel}
                center = {center}>
            </lightning-map>
        </template>        
    </lightning-card>
</template>

displayLocationSubscriber.js

Few important points:
  • Defining the scope where subscriber component receives the message in our application. Here APPLICATION_SCOPE has been used.
  • Scope feature is only available using @wire(MessageContext)
  • There are methods to subscribe, unsubscribe and import message channel for using it.
  • In the subscribe method, you can see arrow function has been used so it can call any methods of the class using this operator. Scope can be passed as parameter.
  • handleMessage() method retrieves the account array and prepare attribute values to be used in lightning-map.
Entire js file as follows:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import { LightningElement, wire, track} from 'lwc';
// Import message service features required for subscribing and the message channel
import {
    subscribe,
    unsubscribe,
    APPLICATION_SCOPE,
    MessageContext
} from 'lightning/messageService';
import selectedEntity from '@salesforce/messageChannel/Selected_EntityId__c';

let acct;
export default class DisplayLocationSubscriber extends LightningElement {
    subscription = null;
    record;

    isDisplayLocation=false;
    @track mapMarkers = []; //holds account location related attributes
    markersTitle = ''; //title of markers used in lightning map.
    zoomLevel = 4;   //initialize zoom level
    center; //location will be displayed in the center of the map

    selectedAccountName = '';

    @wire(MessageContext)
    messageContext;

    // Standard lifecycle hooks used to subscribe and unsubsubscribe to the message channel
    connectedCallback() {
        this.subscribeToMessageChannel();
        this.isDisplayLocation = false;
    }

    disconnectedCallback() {
        this.unsubscribeToMessageChannel();
    }

    // Encapsulate logic for Lightning message service subscribe and unsubsubscribe
    subscribeToMessageChannel() {
        if (!this.subscription) {
            this.subscription = subscribe(
                this.messageContext,
                selectedEntity,
                (message) => this.handleMessage(message),
                { scope: APPLICATION_SCOPE }
            );
        }
    }

    unsubscribeToMessageChannel() {
        unsubscribe(this.subscription);
        this.subscription = null;
    }

    // Handler for message received by component
    handleMessage(message) {
        //assigns account records
        acct = message.record;

        this.selectedAccountName = acct.Name;
        
        //prepares information for the lightning map attribute values.
        this.markersTitle = acct.Name;
        
        this.mapMarkers = [
            {
                location: {
                    Street: acct.BillingStreet,
                    City: acct.BillingCity,
                    State: acct.BillingState,
                    Country: acct.BillingCountry,
                },
                icon: 'custom:custom26',
                title: acct.Name,
            }                                    
        ];
        this.center = {
            location: {
                City: acct.BillingCity,
            },
        };
        this.zoomLevel = 6;
        this.isDisplayLocation = true;
    }    
}

Create a Lightning App Builder page with two regions and place those components and display the page, selecting an account on left side component will display location on right side.

Finally we are done and thanks for reading.


Further Reading


No comments:

Post a Comment