Event Handling between Aura, Lightning Web Components (LWC) and Visualforce

How to use Lightning Message Services for event handling between Lightning Web Components, Aura Components and Visualforce

Salesforce Lightning Message Service

Few months back, I wrote an article on how pub sub model can be used to communicate between Lightning Web Components. In that blog post, we used external library to pass event from child to Lightning Web Components.

Lightning Message Service

In Winter 20, Salesforce released Lightning Message Service which can be used to exchange data between Visualforce, Aura Component and Lightning Web Components. Unlike previous process, we don’t need to import any external library like pub or sub.

In this blog post, I would be creating Visualforce, Aura Component and Lightning Message Service and exchanging message between all of them using Lightning Message Service.

Lightning Message Channel

At the heart of Lightning Message Service, we have Lightning Message Channel. Its basically name of Schema which will hold actual message. For sake of this blog post, create file LMSDemoWin.messageChannel-meta.xml in folder messageChannels.

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
   <masterLabel>LMS Demo</masterLabel>
   <isExposed>true</isExposed>
   <description>Winter 20 - LMS Demo.</description> 
</LightningMessageChannel>

Deploy Lightning Message Channel

Run below SFDX command to deploy this message channel on your Salesforce Org, run push command to deploy message channel on Scratch Orgs.

sfdx force:source:deploy -p "force-app/main/default/messageChannels/"

Once, Lightning Message Channel created, let’s start by creating our components and first we will create below Visualforce Page.

<apex:page>
    <!-- Begin Default Content REMOVE THIS -->
    <h1>Lightning Message Services - Demo - Winter 20</h1>
    <div>
        <p>Message To Send</p>
        <input type="text" id="vfMessage" />
        <button onclick="publishMessage()">Publish</button> 
        <br/> 
        <button onclick="subscribeMC()">Subscribe</button> 
        <button onclick="unsubscribeMC()">Unsubscribe</button> 
        <br/>
        <p>Messages Received:</p>
        <textarea id="txtMessages" rows="2" style=" width:100%;" />
    </div>
    <script> 
        // Load the MessageChannel token in a variable
        var lmsDemoChannel = "{!$MessageChannel.LMSDemoWin__c}";
        var subscriptionToMC;
       function publishMessage() {
            const payload = {
                source: "Visualforce",
                messageBody: document.getElementById('vfMessage').value
            };
            sforce.one.publish(lmsDemoChannel, payload);
        }
        function subscribeMC() {
            if (!subscriptionToMC) {
                subscriptionToMC = sforce.one.subscribe(lmsDemoChannel, onMCPublished);
            }
        }
        function unsubscribeMC() {
            if (subscriptionToMC) {
                sforce.one.unsubscribe(subscriptionToMC);
                subscriptionToMC = null;
            }
        }
        function onMCPublished(message) {
            var textArea = document.querySelector("#txtMessages");
            textArea.innerHTML = message ? 'Message: ' + message.messageBody + ' From: ' + message.source : 'no message payload';
        } 
    </script>
</apex:page>

In above code, we have few functions to subscribe , Unsubscribe and publish Lightning Message Channel. We can follow any schema for payload however that has to be followed by all clients as well. In our case, we are passing source and actual message body.

Next component we would build would be Aura Component

<aura:component description="testMessageAura" implements="flexipage:availableForAllPageTypes" access="global">
    <aura:attribute type="String" name="myMessage"/>
    <aura:attribute type="String" name="receivedMessage"/>
    <lightning:messageChannel type="LMSDemoWin__c" aura:id="lmsDemohannel" onMessage="{!c.handleReceiveMessage}"/>

    <lightning:card title="Aura Component" iconName="custom:custom18">
        <div class="slds-m-around_medium">
            <lightning:input type="text" value="{!v.myMessage}" label="Message To Send"/>
            <lightning:button label="Publish" onclick="{! c.handleClick}"/>
            <br/>
            <br/>
            <p>Latest Received Message</p>
            <lightning:formattedText value="{!v.receivedMessage}"/>
        </div>
    </lightning:card>
</aura:component>	

Controller of Aura Component

({
    handleClick: function(component, event, helper) {
        let myMessage = component.get("v.myMessage");
        const payload = {
            source: "Aura Component",
            messageBody: myMessage
        };
        component.find("lmsDemohannel").publish(payload);
    },
    handleReceiveMessage: function (component, event, helper) {
        if (event != null) {
            const message = event.getParam('messageBody');
            const source = event.getParam('source');

            component.set("v.receivedMessage", 'Message: ' + message + '. Sent From: ' + source);
        }
    }
});

As you can see in above Aura Component, we are not doing anything special to subscribe or unsubscribe Lightning Message Channel. Aura Component by default handles those operations once we declare them.

Last component we would be creating is Lightning Web Component

<template>
    <lightning-card title="LWC" icon-name="custom:custom18">
        <div class="slds-m-around_medium">
            <lightning-input label="Message To Send" type="text" value={_msg} onchange={handleChange}></lightning-input>
            <lightning-button label="Publish" onclick={handleClick}></lightning-button>
            <br>
            <lightning-button label="Subscribe" onclick={handleSubscribe}></lightning-button>
            <lightning-button label="Unsubscribe" onclick={handleUnsubscribe}></lightning-button>
            <p> Message Received</p>
            <lightning-formatted-text value={receivedMessage}></lightning-formatted-text>
        </div>
    </lightning-card>
</template>

Javascript of LWC

import { LightningElement, track} from 'lwc';
import { publish,createMessageContext,releaseMessageContext, subscribe, unsubscribe } from 'lightning/messageService';
import lmsDemoMC from "@salesforce/messageChannel/LMSDemoWin__c";
export default class LMS_MessageSenderReceiverLWC extends LightningElement {
    @track _msg = '';
    @track receivedMessage = '';
    channel;
    context = createMessageContext();

    constructor() {
        super();
    }
   
    handleSubscribe() {
        const parentPage = this;
        this.channel = subscribe(this.context, lmsDemoMC, function (event){
            if (event != null) {
                const message = event.messageBody;
                const source = event.source;
                parentPage.receivedMessage = 'Message: ' + message + '. Sent From: ' + source;
            }
        });
    }

    handleUnsubscribe() {
        unsubscribe(this.channel);
    }

    handleChange(event) { 
        this._msg = event.target.value;
    }

    handleClick() {  
        const payload = {
            source: "Lightnign Web Component",
            messageBody: this._msg
        }; 
        publish(this.context, lmsDemoMC, payload);
    } 

    disconnectedCallback() {
        releaseMessageContext(this.context);
    }
}

Once we embed all three components (Visualforce, Aura and Lightning Web Components), we would be able to see Lightning Message Service in action as shown in below image :

Lightning Message Service in Salesforce

Related posts

10 thoughts on “Event Handling between Aura, Lightning Web Components (LWC) and Visualforce”

  1. I’m getting this error
    We couldn’t find Lightning Message Channel LMSDemoWin__c. Check your spelling and try again

    I have successfully deployed the LightningMessageChannel

  2. Hi Jitendra,

    Have asked this particular requirement related to OpenCTI in multiple channels. dlouvton from salesforce suggested to use OpenCTI lighting message Service( https://github.com/developerforce/open-cti-demo-adapter/issues/37).

    Requirement: Have to make a call from a quick Action but not on clicking phone number.

    Challenges:

    1) Don’t have any similar method like sendCTIMessage() in lighting which works well in classic.
    2) Though I have a hack, we can’t simply raise an onClickToDial event in Lighting aura/Web Components. And VF page in lighting won’t work as VF renders in iframe and end of the day VF renders in Lighting Experience.
    3) My OpenCTI adapter URL in call center is not our SFDC page instead it is a Twilio-Flex(3rd party vendor) URL so I don’t have a any DIRECT control to add the subscribe() code. And this Twilio-Flex is implemented in react.js and I do have a little control where I can add some plugins to this URL. In this plugin I can add Salesforce openCTI_min.js script and use the subscribe() method.

    Options I left with:
    Using this Lighting Message Service for OopenCTI( https://developer.salesforce.com/docs/atlas.en-us.api_cti.meta/api_cti/sforce_api_cti_methods_intro_lightning.htm ). I wanted to publish() a message in my aura/LWC and would like to have this message needed to subscribe() by Twilio-Flex soft-phone which is in same DOM but in react.Js. Can we achieve this using LMS? More specifically, how to configure/get Salesforce Message channel stuff into Flex react.js page though these both are in same DOM.?

    If you are interested, I can provide you the sample code snippets about react.js plugin and little hack (point# 2). But https://github.com/developerforce/open-cti-demo-adapter/issues/37 has most of the requirement what I am talking about.

    Thank you!
    Charan

  3. Hi Jitendra,

    I just copied your examples because I need communication between Aura and LWC.
    When I try to subscribe the LWC to the message I´m getting an error in the next line:


    messageService.subscribe(this.context, lmsDemoMC, function (event)

    when I debug it I´m accessing to:


    function runWithBoundaryProtection(vm, owner, pre, job, post) {
    {
    assert.isTrue(vm && ‘cmpRoot’ in vm, `${vm} is not a vm.`);
    }

    let error;
    pre();

    try {
    job();
    } catch (e) {
    error = Object(e);
    }

    but the excetion value is:
    JSON.stringify(e): “{}”

    Any idea?

    BTW, with both components I´m able to publish messages.
    In the other hand is that the aura component I needed to add the scope:

    Do you know which other scopes are supported?

    Thanks in advance

          1. Sorry David, I am not sure about that and also couldn’t find anything on this, its still in developer preview.

  4. Hi Jitendra, Not able to get this working in Classic application where 1 visualforce page has LWC (receiver) and other has direct Message Channel using sforce.one.publish.

    The sforce.one is not working. This is because of not executing in lightning experience.

    Any hints to patch it up for classic experience ?

  5. HI JItendra,

    I am getting an error while publishing message from one web Component: [error = TypeError: Cannot convert a Symbol value to a string at], it occurs while receiving MessageContext using @wire(MessageContext) mContext;

    console says mContext value = Symbol(MessageContext_43ee57fb_fbb4_4cf1_bd78_c45358de67cd)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.