Lightning Message Service (LMS) to communicate between components

In winter ’20 release (API 47.0) salesforce has introduced Lightning Message Service which can be utilised to communicate between components within a single Lightning page or across multiple pages. The advantage of using a Lightning message channel over pubsub or Aura Application event is that message channels are not restricted to a single page. Any component in a Lightning Experience application that listens for events on a message channel updates when it receives a message. It works between Lightning web components, Aura components, and Visualforce pages in any tab or in any pop-out window in Lightning Experience. 

Some solution might be required to have classic Visualforce page in place. If we need to communicate between a visualforce page (which is hosted as iframe within the lightning component) and a Lightning component, window.postMessage() was the only option. But with LMS, it can be easily achievable.

Creating Lightning Message Channel

Currently we can create Lightning Message channel with Metadata API. You can create the same with the help of VsCode. You need to create one DX project then you need to place your message channel definition with the suffix .messageChannel-meta.xml in the force-app/main/default/messageChannels directory. like below folder structure.Deploy the message channel to your org using sfdx force:source:deploy (even better, sfdx force:source:push in a scratch org), ensuring that you’re using at least API v47. Then your org knows about the message channel, and you can reference it in code.

With this publish subscriber model, any of the component (listed below) can act as a publisher or a subscriber. Below table listed how to achieve every function in different component.

FunctionAuraLWCVF Page
Listen to message
channel
<lightning:messageChannel type="SampleMC__c" onMessage="{!c.handleChanged}" scope="APPLICATION"/>
//pre-requisite to subscribe for payload to the channel
import { subscribe, unsubscribe, APPLICATION_SCOPE, MessageContext } from ‘lightning/messageService’;
//Import the definition for subscribe, unsubscribe, Scope and context of the message
import SAMPLEMC from ‘@salesforce/messageChannel/SampleMC__c’;
//message channel metadata
//pre-requisite to subscribe for payload to the channel
var SAMPLEMC = “{!$MessageChannel.SampleMC__c}”;
//pre-requisite to subscribe for payload to the channel and also for publish to the channel
publish to message channel<lightning:messageChannel type=”SampleMC__c”
aura:id=”sampleMessageChannel”/>
//pre-requisite to publish payload to the channel
import { publish, MessageContext } from 'lightning/messageService';
import SAMPLEMC from '@salesforce/messageChannel/SampleMC__c';
//pre-requisite to publish payload to the channel
publishcomponent.find(‘sampleMessageChannel’).publish(payload);
//publish the payload to the channel
publish(this.messageContext, SAMPLEMC, message);
//publish the payload to the channel
sforce.one.publish(SAMPLEMC, message);
//publish the payload to the channel. Salesforce has introduced new sforce API to publish message to a channel
subscribeonMessage method on the first row will handle the
received payload.
subscribe( this.messageContext, SAMPLEMC, (message) => { this.handleMessage(message); }, {scope: APPLICATION_SCOPE});sforce.one.subscribe(SAMPLEMC, handleMessage);
unsubscribeunsubscribe(this.subscription);sforce.one.unsubscribe(subscription);

Now enough of theory, lets jump into the real world example where we will see how this pub-sub model actually works.

  • Aura component in the above image works as publisher. This will publish account info to the message channel.
  • LWC which is added as a utilityItem will listen to the message channel and whenever a message will be published by Aura, it will be received there. LWC will not explicitly subscribe to the channel but when the component is loaded then only it subscribes to the channel.
  • VF page will explicitly subscribe to the channel by clicking subscribe button and when a payload is published, that will be received by the page.

Aura:

LMS_PublishAccountInfo.cmp

<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId" access="global">
<aura:attribute name="record" type="Object" description="The record object to be displayed" />
<aura:attribute name="simpleRecord" type="Object" description="A simplified view record object to be displayed" />
<aura:attribute name="recordError" type="String" description="An error message bound to force:recordData" />
<!--Lightning Message Channeel where a message can be pubslihed-->
<lightning:messageChannel type="SampleMC__c"
            aura:id="sampleMessageChannel"/>

<force:recordData aura:id="record" layoutType="FULL" recordId="{!v.recordId}" targetError="{!v.recordError}"
    targetRecord="{!v.record}" targetFields="{!v.simpleRecord}" mode="VIEW" />
    <lightning:card>
        <aura:set attribute="title">
            <lightning:icon iconName="standard:account" alternativeText="Account" title="Account" />
            Publish Account Info
        </aura:set>
        <p class="slds-p-horizontal_small">
            <lightning:button variant="success" label="Publish" title="Publish" onclick="{! c.handleClick }"/>
        </p>
    </lightning:card>
</aura:component>	

LMS_PublishAccountInfoController.js

({
    handleClick : function(component, event, helper) {
        const payload =
        {
            recordId: component.get('v.recordId'),
            recordData : {
                Name: component.get('v.simpleRecord.Name'),
                Industry: component.get('v.simpleRecord.Industry')
            }
        }
        console.log(payload);
        component.find('sampleMessageChannel').publish(payload);
    }
})

LWC:

lMS_subscribeMessageChannel.html

<template>
    <lightning-card title="Listen Account Data" icon-name="custom:custom14">
        <div class="slds-m-around_medium">
            <p>Received message:</p>
            <textarea id="receivedMessageTextArea" class="textareaReceivedMessage" rows="9" disabled>{receivedMessage}</textarea>
        </div>
    </lightning-card>
</template>

lMS_subscribeMessageChannel.js


import { LightningElement, wire } from 'lwc';
import { subscribe, unsubscribe, APPLICATION_SCOPE, MessageContext } from 'lightning/messageService';

import SAMPLEMC from '@salesforce/messageChannel/SampleMC__c';

export default class lMS_subscribeMessageChannel extends LightningElement {
    @wire(MessageContext)
    messageContext;

    subscription = null;
    receivedMessage;

    connectedCallback(){
        if (this.subscription) {
            return;
        }
        this.subscription = subscribe(
            this.messageContext,
            SAMPLEMC, (message) => {
                this.handleMessage(message);
            },
            {scope: APPLICATION_SCOPE});
    }
    disconnectedCallback(){
        unsubscribe(this.subscription);
        this.subscription = null; 
    }

    handleMessage(message) {
        this.receivedMessage = message ? JSON.stringify(message, null, '\t') : 'no message payload';
    }
}

Calling subscribe method in the connectedcallback function, so whenever the component is getting loaded it will subscribe to the channel. In this similar way, pubsub module works.

Unsubscribe from channel in disconnectedcallback.

Since LWC can’t been added as utilityItem we need a wrapper Aura Component to add the LWC there.

<!--LMS_WrapAccountDataListenerLWC-->
<aura:component implements="flexipage:availableForAllPageTypes">
    <c:lMS_subscribeMessageChannel/>
</aura:component>

VF Page

LMS_SubscribeToLMS.page

<apex:page standardController="Account">
  
    <div>
        <p>This VF page will subscribe to the LMS to receive the Account Payload</p>
            <button onclick="subscribeMC()">Subscribe</button>
            <button onclick="unsubscribeMC()">Unsubscribe</button>
        <br/>
        <br/>
        <p>Received message:</p>
        <label id="MCMessageText"/>
    </div>
  
    <script>
      
        // Load the MessageChannel token in a variable
        var SAMPLEMC = "{!$MessageChannel.SampleMC__c}";
        var subscriptionToMC;
      
        // Display message in the textarea field
        function displayMessage(message) {
            var textLabel = document.querySelector("#MCMessageText");
            textLabel.innerHTML = message ? JSON.stringify(message, null, '\t') : 'no message payload';
        }

        function subscribeMC() {
            if (!subscriptionToMC) {
                subscriptionToMC = sforce.one.subscribe(SAMPLEMC, displayMessage, { scope: "APPLICATION" });
                console.log('message recvd',displayMessage);
            }
        }

        function unsubscribeMC() {
            if (subscriptionToMC) {
                sforce.one.unsubscribe(subscriptionToMC);
                subscriptionToMC = null;
            }
        }

    </script>

</apex:page>

See the video

Resource guide: https://developer.salesforce.com/blogs/2019/10/lightning-message-service-developer-preview.html

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_message_channel

Invoking Continuation class from Lightning Component

Asynchronous callouts (also referred to as Apex continuations) allow you to invoke long-running services without tying up server resources and running into Apex concurrency limits. An asynchronous callout made with a continuation doesn’t count toward the Apex limit of 10 synchronous requests that last longer than five seconds. Therefore, you can make more long-running callouts and integrate your component with a complex back-end API.

Natively salesforce didn’t allow Lightning component (Aura and LWC) to invoke Continuation apex. It was only supported from Visualforce pages. But with Summer ’19 release, an Aura component or a Lightning web component can use the Continuation class in Apex to make a long-running request to an external web service. Isn’t that good news !!!

This wonderful blog post describes still how you can invoke apex continuation from lightning component which was not yet supported natively by salesforce. This requires the usage of Visualforce page embedded as an iframe in the Lightning component. This VF page will then do the magic to invoke Continuation apex and post the response back to Lightning component.

But with the new release, you can easily invoke the Continuation apex from Lightning component. Below is the example with the details. But before starts, you need to register for the Summer 19 pre release org.

Here is the apex class that use the continuation and make the long running callout.

@AuraEnabled(continuation=true cacheable=true)

    global static Object getProduct(Integer productId, Integer latency){

 // Make an HTTPRequest as we normally would

        // Remember to configure a Remote Site Setting for the service!

        String url = 'https://long-running.herokuapp.com/products/' + productId + '?latency=' + latency;

        HttpRequest req = new HttpRequest();

        req.setMethod('GET');

        req.setEndpoint(url);
        // Create a Continuation for the HTTPRequest        
       //Timeout in seconds, 60 is limit
        Continuation con = new Continuation(60);

        con.state = con.addHttpRequest(req);

        con.continuationMethod = 'callback';     
        // Return it to the system for processing

        System.debug('returned from server'+con);

        return con;

    }

Use continuation=true and cacheable=true with AuraEnabled annotation to invoke it from lightning component. You have to define the call back method which process the response asynchronously.

@AuraEnabled(cacheable=true)

    global static Object callback(Object state) {
        HttpResponse response = Continuation.getResponse((String)state);
        Integer statusCode = response.getStatusCode();
        if (statusCode >= 2000) {
               return 'Continuation error: ' + statusCode;
          }
        System.debug('response @@'+response.getBody()+'Status code'+statusCode);
        return response.getBody();
    }

Use @AuraEnabled(cacheable=true) annotation with the callback method to access the response from the Lightning component.

In this example, i have used https://long-running.herokuapp.com/products/ service where you can set the latency of the transaction. So that you can experience the actual long running query behaviour.

Here is the full code of the apex class and aura component.

Please see below the video of the working example. You can see when the latency is 2000 , long running query takes longer time time than the next callout where the latency is 20.

Resource: Salesforce Release note summer 19 – Long running queries from Lightning.

Use Lightning Web Components in Visualforce Pages

With Summer ’19 release, salesforce has introduced the use of lwc in visualforce pages. It will be used in same way like any aura components before.

Before starts, please sign up for pre-release developer edition.

Steps to add the Lightning web component in the visualforce page.

  1. Add the Lightning Web Components for Visualforce JavaScript library to your Visualforce page using the <apex:includeLightning/> component.

<apex:page>
  <apex:includeLightning />

……

</apex:page>

2. Create and reference a standalone Aura app that declares your component dependencies.

<aura:application extends=”ltng:outApp” access=”GLOBAL”>
<aura:dependency resource=”c:demolwcforLighntingout” />
<aura:dependency resource=”markup://force:*” type=”EVENT”/>
</aura:application> 

Below is the lwc definition

<template>
<lightning-card title=”Hello” icon-name=”custom:custom14″>
<div class=”slds-m-around_medium”>
Hello, {greeting}!
</div>
</lightning-card>
</template>

import { LightningElement,api } from ‘lwc’;

export default class DemolwcforLighntingout extends LightningElement
{
@api greeting ;
}

3. Write a JavaScript function that creates the component on the page using $Lightning.createComponent().

<apex:page>
  <apex:includeLightning />
  <div id=”container”>
</div>

<script>
$Lightning.use(“c:lwcContainerApp”, function()
{
$Lightning.createComponent(
“c:demolwcforLighntingout”,
{‘greeting’:’Somnath’},
“container”,
function(cmp)
{
console.log(‘component created’);
});
});
</script>
</apex:page>


Note: You can pass arguments to the lightning web components. In lwc, that property must be publicly accessible. So marking it with @api.

Use namespace:camelCaseComponentName naming convention to reference the Lightning web component.

Resources: https://releasenotes.docs.salesforce.com/en-us/summer19/release-notes/rn_lwc_vf.htm

Lightning Web Components

Salesforce has introduced a new programming model for lightning components called ‘Lightning Web Components’. It leverages web standards breakthroughs, can coexist and interoperate with the Aura programming model, and delivers unparalleled performance. But to use that we need sfdx.

This blog post will show you how to create a very basic LWC and to understand the core concept of it.

We need pre-release developer edition to work on this exercise. Once you are singed in, please enable the dev hub.

VS code can be used to easily achieve this. We can use all the pre build commands to easily create lightning web components.

To work with the lighting web component, you need to install‘Lightning Web Component’ extension in vs code.

Once this is installed, you can follow the below step tocreate a Lightning web components.

  1. Create a sfdx project.
  • Authorize a dev-hub (Use the same accountthat you created now in pre-release edition)
  • Create a default scratch org using default using default projet-scratch-def.json

  • sfdx force:org:create -f config\project-scratch-def.json–setalias Mylwc –durationdays 7 –setdefaultusername
  • Create a apex class which will be controller ofthe lwc.

Add the controller method

public with sharing class LoadContact {

    public LoadContact() {

    }

    @AuraEnabled(cacheable=true)

    public static List<Contact> getContactList(){

        return [Select Id,FirstName,LastName,Email from Contact];

    }

}


Here comes the main attraction – Lightning Web Components

Upon creation of LWC, it will create a three files by default.

  1. LoadContact.html
  2. LoadContact.js
  3. LoadContact.js-meta.xml
  1. LoadContact.html

<template>

    <lightning-card title=”LoadContact” icon-name=”custom:custom63″>

        <div class=”slds-m-around_medium”>

            <p class=”slds-m-bottom_small”>

                <lightning-button label=”Load Contacts” onclick={handleLoad}></lightning-button>

            </p>

            <template if:true={contacts}>

                <template for:each={contacts} for:item=”contact”>

                    <p key={contact.Id}>{contact.Name}</p>

                </template>

            </template>

        </div>

    </lightning-card>

</template>

Template: The template element is used to declare fragments of HTML that can be cloned and inserted in the document by script.

  • LoadContact.js

import { LightningElement, track } from ‘lwc’;

import getContactList from ‘@salesforce/apex/LoadContact.getContactList’;

export default class LoadContact extends LightningElement {

    @track contacts;

    @track error;

    handleLoad() {

        getContactList()

            .then(result => {

                this.contacts = result;

            })

            .catch(error => {

                this.error = error;

            });

    }

}

LightningElement: Base class for the Lightning Web Component JavaScript class

The following piece of JS code determine which Server side apex controller to trigger:

import getContactList from ‘@salesforce/apex/LoadContact.getContactList’;

track: Decorator to mark private reactive properties

  • LoadContact.js-meta.xml

<?xml version=”1.0″ encoding=”UTF-8″?>

<LightningComponentBundle xmlns=”http://soap.sforce.com/2006/04/metadata” fqn=”LoadContact”>

    <apiVersion>45.0</apiVersion>

    <isExposed>false</isExposed>

    <targets>

        <target>lightning__AppPage</target>

        <target>lightning__RecordPage</target>

        <target>lightning__HomePage</target>

    </targets>

</LightningComponentBundle>

This xml file determine where these lwc can be used.

isExposed flag determines if you are exposing component to push to your scratch org. By default the value is false. Remember to make it true before you push.

Run the below command to check the difference between your local and scratch org.

Below is the result:

Push the changes to scratch org.

Changes will be pushed from your local to your scratch org.

Now this custom Lightning Web Component is available to you use in lightning app builder pages.

If you click on Load contact button it should retrieve all the available contacts from your org. It makes server side apex call to fetch the same.

In this Lightning web component Series, I will go in much deeper concept in my next posts. I will cover what is the differences between LWC and aura framework and what is performance improvement factors of this new frame work.

Please add/post your valuable comments.

Happy learning !!!!