Before LMS, if you wanted a component in the left sidebar to update a component in the main page area, developers had to rely on complex URL hacks, window events, or unsupported custom Pub-Sub libraries. Lightning Message Service completely solved this by providing a secure, standardized, and metadata-driven messaging architecture built natively into the Salesforce platform.
📌 Key Points
- Decoupled Architecture: Publishers don't need to know who is listening, and subscribers don't need to know who is sending.
- Cross-Framework: A single message channel can connect Lightning Web Components (LWC), Aura components, and legacy Visualforce pages simultaneously.
- Metadata-Backed: Channels are not just JavaScript files; they are official Salesforce metadata artifacts ending in
.messageChannel-meta.xml. - Scope Control: You can broadcast messages to the entire application (across tabs) using
APPLICATION_SCOPE, or restrict it to the active component tree.
- Message Channel: The XML definition of the "radio station."
- Publisher: The component using the
publish()method. - Subscriber: The component using the
subscribe()method. - Message Context: The
@wire(MessageContext)adapter that links the component's lifecycle to the LMS framework.
📡 Step 1: Create the Message Channel Metadata
Unlike standard custom events, LMS requires you to define a channel at the org level using XML. This makes the channel reusable and discoverable across your entire Salesforce org.
force-app/main/default/messageChannels/RecordUpdateChannel.messageChannel-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
<masterLabel>RecordUpdateChannel</masterLabel>
<isExposed>true</isExposed>
<description>Broadcasts selected record IDs across unrelated components.</description>
<lightningMessageFields>
<fieldName>recordId</fieldName>
<description>The 18-character Salesforce Record ID.</description>
</lightningMessageFields>
<lightningMessageFields>
<fieldName>recordData</fieldName>
<description>Additional payload string for context.</description>
</lightningMessageFields>
</LightningMessageChannel>
__c SuffixEven though this is not a Custom Object, you must append
__c to the end of your channel name when importing it into your JavaScript file (e.g., RecordUpdateChannel__c). Forgetting this will cause a compilation error!
📤 Step 2: Build the Publisher Component
The publisher component imports the channel and uses the publish() method to send a JSON payload into the ether.
// lmsPublisher.js
import { LightningElement, wire } from 'lwc';
import { publish, MessageContext } from 'lightning/messageService';
import RECORD_UPDATE_CHANNEL from '@salesforce/messageChannel/RecordUpdateChannel__c';
export default class LmsPublisher extends LightningElement {
// Wires the component to the LMS framework
@wire(MessageContext)
messageContext;
handlePublishClick() {
// Construct the payload matching your XML fields
const payload = {
recordId: '0018W00002E3XYZ',
recordData: 'High Priority Account'
};
// Broadcast the message
publish(this.messageContext, RECORD_UPDATE_CHANNEL, payload);
}
}
📥 Step 3: Build the Subscriber Component
The subscriber listens for messages on the channel. It is crucial to handle subscriptions properly so they are active when the component is on screen, and safely destroyed when the user navigates away.
// lmsSubscriber.js
import { LightningElement, wire } from 'lwc';
import { subscribe, unsubscribe, APPLICATION_SCOPE, MessageContext } from 'lightning/messageService';
import RECORD_UPDATE_CHANNEL from '@salesforce/messageChannel/RecordUpdateChannel__c';
export default class LmsSubscriber extends LightningElement {
receivedRecordId = 'Waiting for broadcast...';
subscription = null;
@wire(MessageContext)
messageContext;
// Subscribe when the component is inserted into the DOM
connectedCallback() {
this.subscribeToChannel();
}
// Safely unsubscribe when the component is removed
disconnectedCallback() {
this.unsubscribeFromChannel();
}
subscribeToChannel() {
// Prevent duplicate subscriptions
if (!this.subscription) {
this.subscription = subscribe(
this.messageContext,
RECORD_UPDATE_CHANNEL,
(message) => this.handleMessage(message),
{ scope: APPLICATION_SCOPE }
);
}
}
unsubscribeFromChannel() {
if (this.subscription) {
unsubscribe(this.subscription);
this.subscription = null;
}
}
handleMessage(message) {
this.receivedRecordId = `Received: ${message.recordId} - ${message.recordData}`;
}
}
🚨 4. Common Traps & Best Practices
Technically, if you use
@wire(MessageContext), Salesforce automatically cleans up the subscription when the component is destroyed. However, relying purely on auto-teardown is risky if you ever conditionally pause subscriptions or switch to createMessageContext(). Best Practice: Always explicitly call unsubscribe(this.subscription) inside your disconnectedCallback() to guarantee you never create memory leaks or ghost event listeners.
- Use APPLICATION_SCOPE wisely: Passing
{ scope: APPLICATION_SCOPE }into yoursubscribe()method allows your component to hear messages across different Lightning Console sub-tabs or standard browser tabs. If omitted, it defaults to the active navigation context only. - Keep Payloads Lightweight: Do not pass massive arrays of queried data through LMS. Pass a
recordIdand a status flag, and let the receiving component query the fresh data itself using Lightning Data Service (LDS).
❓ Core Interview Q&A
Q: What is the difference between Lightning Message Service (LMS) and Platform Events?
Comments
Questions, corrections and your own experience are all welcome.
Post a Comment