Within Deloitte we have a very ambitious Lightning Team which encourages all colleagues to improve their Lightning Web Components (LWCs) skills. One of their brilliant ideas is “The Forgetful Consultant”; a fictive persona who needs some help to support his/her daily job. Frequently, the team shares another way you can support the Consultant by developing some cool LWC. Each challenge highlighting a certain LWC feature and becoming gradually more challenging.

End of May, I’ve completed the fourth challenge which focused on Sibling Communication. Not only communicating up and down the DOM, but to any component on the page, using PubSub and Lightning Messaging Service (LMS). With LMS you can even communicate easily between ‘languages’ (e.g. from a LWC to a Visualforce or Aura component and back)!

This challenge was to develop a Style Distributor which would inform other components on the page that their styling (CSS) should be updated to be aligned with the rest on that page. Because of the reusability and match with the previous post on CSS Variables, I thought it would be nice to share you the end-result.

Article Structure

Since this is the first time I’m sharing a hobby project, having the article cover more than one technique, I’m curious about your experience on article structure and clarity. Please reach out if you find any improvement points 🙂 You can use the Table of Content below, or in the sidebar on the right to easily navigate through this article.

The main goal of this article is to share how to apply CSS Variables, the PubSub library and Lightning Messaging Service. Diving into quirks and discussing advantages and challenges. I hope you might be able to reuse the component to broadcast styles to any component on your page, leverage the improvements to the PubSub library or can benefit from applying Default CSS Variable approach.

  1. Overview of total component (end-result);
  2. CSS Variables, retrieving and setting them using JavaScript (both in LWC and Aura components);
  3. PubSub, LWC recipe library which some home-made modifications;
  4. Lightning Messaging Service, how to apply and the current limitations
  5. Conclusion

All code can be found in the Repository and a working Demo in my shared Community.

Community Disclaimer
– Unfortunately, the Lightning Messaging Service is in Beta and not yet available in Communities (nor Mobile or Lightning Out). Hence, only the PubSub events are working in the shared Demo.
– In addition, Salesforce has already updated their CSS within the Lightning Experience to incorporate CSS variables, while their Community CSS still hard-codes the NetworkingBranding input. This prevents us from overriding the slds-styling (like the slider-thumb), as those are Web Components with a Scoped CSS (thus, can’t be overruled by a parent component).

The Style Distributor Component

Initial load Initial page load
Example Events After 6 events where a.o. colour and font-unit changed

Above you find two screenshots of this project. The Lightning (Flexi-)page includes three components:

  1. LWC: fc_styleDistributor
    The core component. One can alter the default accent colour and font-size after which (conditionally) the updates are published using both/either PubSub and/or Lightning Messaging Service.
    Thanks to CSS Variables, it only took 2 lines of CSS to overrule the slider-thumb and colour-picker-buttons change colours;
  2. LWC: fc_UserInfo
    Component showing some content about the running user. Most relevant is the PubSub Subscription to StyleUpdates and the corresponding update to the components CSS variables;
  3. Aura: FC_MessageChannel_Subscriber
    Component listing received Messages from MessagesChannel and updating header-styling to shared styles.

CSS Variables

In the previous post you could read the theory behind CSS variables and why this could be useful. In this section you’ll find the exact implementation, especially what CSS Selectors to use and how to easily alter the values using JavaScript.

Lightning Web Component

CSS variables in Lightning Web Components are easiest to define at the highest level, :host. This CSS Selector kind-of represents the outer body of your Web Component.

:host{
    --accent-color:     orange;
    --font-size:      	12px;
}

To update or set a CSS variable value, you can use the host variable and easily set the Property via the Javascript CSSStyleDeclaration object:

// Update existing value from orange to purple
this.template.host.style.setProperty( '--accent-color', 'purple' );
// Initiate a new property
this.template.host.style.setProperty( '--content-width', '80%' );

For the retrieval of a CSS variable value, you can use a similar approach, but will need to use the getComputedStyles(). This provides an object with all applicable styles, both inherited from parent elements and default browser values.

let computedStyles = window.getComputedStyle( this.template.host, null );
let accentColor = computedStyles.getPropertyValue( '--accent-color' );

Retrieval on runtime
In case you want to retrieve the initial values on runtime (aka on initiation, like done in fc_StyleDistributor.js), it is important to be aware of the Component life-cycle. The CSS variables are set during rendering and therefore the soonest we can retrieve them is in renderedCallback(); For the retrieval of CSS variables on button push or any other ‘post-component-render’ event, the ComputedStyle can be called in that specific method.

Salesforce documentation on renderedCallback();
Called after every render of the component. This hook flows from child to parent. ( … ) Due to mutations, a component is usually rendered many times during the lifespan of an application.

/**
 * After first time rendering of component, capture the initial values
 */
_hasRendered = false;
renderedCallback(){
  if( !this._hasRendered ){
    // Get computed styles to get the initial :host{} variable values
    let computedStyles = window.getComputedStyle( this.template.host, null );
    let accentColor = computedStyles.getPropertyValue( '--accent-color' );
    
    // Set rendered to TRUE after first rendering, as this method is called for each child and DOM update
    this._hasRendered = true;
  }
}

Example: Child component with default CSS Variables

Imagine a child component, which is designed to inherit CSS variables from the calling component (parent). To increase the reusability of this component, one should define ‘default CSS variable values’ to allow the component to be called by any component, without requiring the definition of CSS variables (in the calling/parent component). Else, the CSS variables will be undefined, causing a poorly/not designed component.

Unfortunately, there is no standard way of setting a default value, to mitigate the scenario that no CSS variable was set by the parent. The fallback variable is only applied when a variable is invalid/malformed, but ignored when no variable was found.

The below code-snippet uses JavaScript to verify whether the CSS variable was set during rendering and defines the default value otherwise.

_hasRendered = false;
renderedCallback(){
  if( !this._hasRendered ){
    // Verify whether :host{} variables are inherited from the parent
    let computedStyles = window.getComputedStyle( this.template.host, null );
    let accentColor = computedStyles.getPropertyValue( '--accent-color' );
    
    // When accent color was not yet defined, define the variable
    if( !accentColor || accentColor.length == 0 ){
      this.template.host.style.setProperty( '--accent-color', 'purple' );
    }
    
    this._hasRendered = true;
  }
}

Aura Components

Using CSS variables in Aura components is a bit more challenging, since the compiler doesn’t accept CSS variables to be defined in the style-component. Therefore, one will need to set the CSS variables in the .cmp file itself (and not in the .css file).

Eventually, I found two ways of defining CSS variables in Aura Component, of which only one allows JavaScript updates.

<aura:component>
  <!-- Body -->
  <aura:html tag="style">
    :root{
    	--accent-color:   lavender-blue;
    }
  </aura:html>
</aura:component>

The first option allows to set them in an uncluttered way, specifying a style tag using the aura:html component. I read some posts where people experienced challenges after sprint ’19, but for me the above worked fine.

The second option, is a bit more cluttered and harder to read/maintain, but does allow easy manipulation via JavaScript. For this approach, you specify the variables in the style tag of the most-outer DOM element. This way, they can be easily fetched in the Controller. Initially you could fetch the first DOM element by cmp.getElement();, but that is not working anymore. You can fetch the element after first ‘finding’ it.

<aura:component>
  <!-- Body -->
  <div style="--accentColor: #f99732; --padding: 2;" aura:id="styleDiv">
	<!-- All items which should adhere to the style variables -->
  </div>
</aura:component>
({
  awesomeMethod: function( cmp, event, helper ){
    // Update CSS variables with new value
    let outerDivStyles = cmp.find( "styleDiv" ).getElement().style; // Get styleDiv item
    let padding = outerDivStyles.getPropertyValue( '--padding' );
    outerDivStyles.setProperty( "--accentColor", 'dark-green' );
    outerDivStyles.setProperty( "--padding", ( padding * 3 - 2 ) );
})

For Aura, I would highly suggest to consciously weigh both solutions and see which one applies best for your scenario (need for JS updates, or preference for readability). Although the best suggestion might be to go for Lightning Web Components 🙂

PubSub

In the absence of the Lightning Messaging Service, Salesforce provided a (custom) library which allowed a Publish-Subscribe mechanism to support Sibling Component communication. Each component on a page can communicate to another component, without the need of Application Events and such.

PubSub will be deprecated in one or two Salesforce releases
At the moment of writing, the PubSub component is still available in the lwc-recipe GitHub, but I’ve been told they’ll replace this with the Lightning Messaging Service (even though LMS is not yet supported by all Salesforce products, like Communities, Lightning Out and Mobile). For archiving purposes, a copy can be found in my Repo, including some nice improvements.

Implementing PubSub

To implement this Publication-Subscription mechanism, one should do the following:

  1. In each component, retrieve the CurrentPageReference and import the relevant methods of the PubSub library
  2. Have components which subscribe
  3. Have component(s) which publish

Technical explanation: Before discussing how to implement, a little intermezzo on what PubSub really entails. In short, the library consists of one Map/object, which contains a List of Subscriber-Components and their callback-method definitions per Name of an Event. When a component subscribes, a reference to that component (and the preferred callback-method which should be fired to process the payload) is added to that Map/object, referencing the specific EventName. After a component publishes some data for that specific EventName, the PubSub component will simply loop over the List of Components and call the callback-methods for each listed component.

To ensure the components are indeed on the same page and are allowed to communicate, the PubSub library makes use of the PageReference.

{
  STYLE_EVENT: [ { callbackMethod1, comp1 }, { callbackMethod2, comp2 } ],
  OBJECT_EVENT: [ { callbackMethod3, comp3 } ]
}

1. Import PubSub & Retrieve PageRef

It is rather simple and you can easily copy-paste, but without it will not work :).

import { LightningElement, wire } from 'lwc';
import { CurrentPageReference } from 'lightning/navigation';
// One only requires either FireEvent to Publish, or the others to Subscribe
import { registerListener, unregisterAllListeners, fireEvent } from 'c/pubsub'; // Make sure you've saved pubsub to your SF environment

export default class PubSub_demoComponent extends LightningElement {
    // Retrieve the current PageReference to allow PubSub to work
    // Don't change the variable name as it is referenced in the library
    @wire( CurrentPageReference ) pageRef;
}

2. Subscribing to Publications

To subscribe to publications, one simply registers a listener (add the callback-method to the list). For this, the event identifier should be known to both the Publisher and Subscriber and the Subscriber should define a function which will process the Published data.

export default class PubSub_Subscription extends LightningElement {
  connectedCallback(){
    registerListener( 'myEvent', this.handlePayload, this );
  }

    handlePayload( payload ){
      console.log( 'Received ', JSON.stringify( payload ) );
    }

  disconnectedCallback() {
    // unsubscribe from styleChange event when component is removed from DOM
    unregisterAllListeners( this );
  }
}

Depending on your use-case a component might be removed from the page (e.g. when loading the component in a tab / accordion component). If you would leave the reference to your component inside the PubSub Map/Object, this would cause a reference-exception on the next Publication. To prevent that scenario, one should always unregister.

3. Publishing Payloads

As simple as the Subscription was, as simple is the Publishing of payloads. One simply asks the PubSub to loop over all Callback-methods related to the Event-Identifier and call those, passing in the provided payload/data.

fireEvent( this.pageRef, 'myEvent', { text: 'Hello World' } );

In the above example, we’re passing an object / payload with the text parameter set to ‘Hello World’

Tips while working with PubSub

Based on some (limited experience) I’d suggest the following:

  • Define a constant in your Publishing component, to prevent hard-coded event names throughout your code. The Subscribing component can simply import: import { EVENT_NAME } from 'c/publisher';;
  • When you want each Subscriber to receive a welcome / initial payload, notify the Publisher when a Subscriber joins ‘later’ (after the Publisher went through the list of Subscriber first). This allows the Publisher to resend the current state / payload (see below).
  • In doubt how the above statements should join together? Check this project specific repository to see how the Lightning Web Components are structured.

PubSub enrichments

The forgetful consultant wanted a Style Distributor which informed all other components on the page on what styles should be applied. As there are no forced relations between the components on the page (like parent/child and such), there is also no dependency in the order of occurrence on the page. Ideally, the Subscribers get rendered first (having the Publisher loaded at the end of the page), allowing the Publisher to directly inform all Subscribers on page load. However, due to the flexibility, this is not restricted and it should also be possible to put the Publisher on top, causing the Subscribers to subscribe after the Publisher is rendered.

Hence, to make this component reusable and context / DOM independent, it is important that Publishers are notified when a new component is Subscribed, allowing the current style-payload to be broadcast.

To realise, we can apply the PubSub in reverse. The registration of a new Subscribing component will fire a payload, informing anyone of this new Subscriber. Then, the original Publisher can subscribe to the ‘notification-event’ and have the payload-publishing as callback-function to those events. In other words: the Publisher will fire EventA when capturing events from EventA_onRegister; The Publisher is Subscribed to Event_onRegister and Publishes EventA.

In the below code you find the newly introduced onNewListeners method, which takes care of the different EventName and such. By this method, the developer again doesn’t need to understand how it works, but it will work from him/her :).

import { LightningElement, wire } from 'lwc';
// Import PubSub libraries and define Event name
import { fireEvent, onNewListeners } from 'c/pubsub';
import { CurrentPageReference } from 'lightning/navigation';
export const EVENT_NAME = 'myEvent';

export default class PubSub_Publisher extends LightningElement{
  @wire( CurrentPageReference ) pageRef;
  
  _payLoad = { name: 'User name' };
  _hasRendered = false;
  renderedCallback(){
    if( !this._hasRendered ){
      // Publish an 'initial welcome event' to already subscribed listeners
      this.publishPayload();

      // Publish an 'initial welcome event' to new listeners
      onNewListeners( EVENT_NAME, this.publishPayload, this );
      this._hasRendered = true;
    }
  }

  publishPayload(){
    fireEvent( this.pageRef, EVENT_NAME, this._payLoad );
  }
}

Lightning Messaging Service

For Salesforce, the Lightning Messaging Service (LMS) is the new way of component communication. It combines the logic of Platform Events and the Streaming API onto Page Context. Likewise to Platform Events, one needs to setup a ‘channel’, allowing to configure the payload fields (types and such). Every component, independently of technique (Visualforce, Aura Component or LWC), can easily listen to or send events related to that channel.

Consideration
At the moment of writing, there are still several shortcomings / considerations before implementing LMS. A.o. the technique is not yet available in Communities, on Mobile or via Lightning Out. And the Channels needs to be created via Metadata, as this is not yet enabled in Setup UI.

Personally, I would only leverage LMS when having multiple techniques on one page which need to communicate, or when sharing larger or more complex payloads across components. When only publishing simple data (e.g. like an error message for a ToastEvent), I would always remain in favour of PubSub. But that is mostly because it is easier to setup and use, and is already working in all Salesforce products.

Implementing Lightning Messaging Service

  1. Create a LightningMessageChannel
  2. Reference the channel in VF, Aura and/or LWC and start publishing and subscribing!

Create LightningMessageChannel

As mentioned, a LightningMessageChannel is created over Metadata API, like one does for Aura Application and Component Events. Although, at the time of writing (June 2020), there is no support in Developer Console and such. Therefore, one should create an additional folder in his/her IDE called ‘messageChannels‘ and in there create a file: ‘channelName.messageChannel‘. An example messageChannel can be found in the repo of this article and below:

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
	<masterLabel>StyleUpdateChannel</masterLabel>
	<description>Channel to support Style Updates for LWC Style Selector to existing Aura and VF components.</description>
	<isExposed>false</isExposed>
	<lightningMessageFields>
		<fieldName>styleObj</fieldName>
		<description>JSON of Style Object</description>
	</lightningMessageFields>
</LightningMessageChannel>

One can see the definition of masterLabel (name to be referenced) and the fields which can be set in the payload. For all possible attributes, you can have a look at the Metadata API Developer Guide.

Reference the channel

In every technique, the channel is referenced in the standard Salesforce object notation, e.g. namespace__masterLabel__c. While each technique has it’s own syntax, the logic is quite comparable. For full documentation I refer to the Salesforce Developer Blog, as it will remain being updated.

Below, one will as well find some simplified versions per technique, excluding all highly recommendable null-pointer checks and disconnect functions. In the examples below every component tries to subscribe and publish to the ChannelName MessageChannel. This MessageChannel is defined with one fields, name which contains the name of the currently logged in user.

LMS in Lightning Web Components

Comparable to other functions in LWC one simply imports the library of functions and the relevant schema (in this case of the MessageChannel) and can directly apply the logic. Note, that per Spring ’20 the scope has become required when subscribing to a MessageChannel.

import { LightningElement, wire } from 'lwc';

// Import messageService libraries and relevant Channel
import { MessageContext, APPLICATION_SCOPE, publish, subscribe } from 'lightning/messageService';
import MESSAGE_CHANNEL from '@salesforce/messageChannel/ChannelName__c';

export default class LightningMessageChannelExample extends LightningElement{
  // Get MessageContext for Lightning Message Service
  @wire( MessageContext ) messageContext;
  
  connectedCallback(){
    subscribe( this.MessageContext, MESSAGE_CHANNEL, ( message ) => {
      console.log( 'Retrieved ', message.name );
    }, { scope: APPLICATION_SCOPE } ); // Required per Spring '20
  }
  
  publishEvent(){
   publish( this.messageContext, MESSAGE_CHANNEL, { name: 'Reinier' } );
  }
}

LMS in Aura Components

In Aura, the import of schema and message handler are combined in one statement in the component. The lightning:messageChannel tag not only references the Channel Name, but also defines the callback-function (onMessage) and the aura:id to allow the publish-controller-function to fetch the definition before sending.

<apex:component>
  <lightning:messageChannel type="ChannelName__c" onMessage="{!c.processMessage}" scope="APPLICATION" aura:id="MessageChannel" />
</apex:component>
({
  processMessage: function( cmp, message, helper ){
    if( message && message._params ){
      console.log( message.getParam( 'name' ) );
    }
  },
  
  publishMessage: function( cmp, event, helper ){
    cmp.find( "MessageChannel" ).publish( { payload: true } );
  }
})

Note, as shown in the above, there are multiple ways of retrieving a field from the MessageChannel. Most recommended is the getParam-function, as Salesforce will update this, when they every decide to rename _params. Hence, your code will be more future-proof / robust.

LMS in Visualforce

As mentioned, one should remain aware $MessageChannel has (currently) only been made available within Lightning (and thus not yet in Communities and Mobile). Your VF page might therefore behave unexpectedly when used outside of Lighting.

<apex:page>
  <script>
    // Load Message Channel to variable
    var LMS_CHANNEL = "{!$MessageChannel.ChannelName__c}";
    var subscription;

    function subscribeLMS(){
      if( !subscription ){
        subscription = sforce.one.subscribe( LMS_CHANNEL, handleMessage );
      }
    }
    
    function handleMessage( message ){
      console.log( 'Retrieved ', message.name );
    }
    
    function publishLMS(){
      sforce.one.publish( LMS_CHANNEL, { payloadField: true } );
    }
  </script>
</apex:page>

Conclusion

This (extensive) article, overall showcases a component which broadcasts a certain style to all components on a page. Enabling one to develop reusable and robust components which can inherit any style and can therefore be used in many different scenarios (e.g. on different Communities / Storefronts or inheriting the App main color and such).

In principle, the article focused on communication across sibling components, which are not related via the DOM, other then being positioned on the same page. For this Sibling communication, both PubSub and Lightning Messaging Service were used. My personal experience is that LMS will be pushed by Salesforce to become the future way of communication across Components of any technique (Visualforce, Aura or LWC). However, at the moment I would limit the usage to only complex or multi-variable structured payloads, since there are still some limitations to Salesforce Products (only working within Lightning, so excluded for Communities, Mobile and LightningOut).

Though the PubSub library was initially shared by Salesforce in their lwc-recipe repository, it was shared this will be replaced by LMS components. Therefore, the latest version of the PubSub class, including two enhancements can be found in this repository.

Furthermore, this article continued on the previous article on CSS Variables. Sharing some practicalities in the usage of CSS variables and how to define, use and update those in both Aura and Lightning Web Components.

In case of any comments, questions or challenges, feel free to reach out! I would love to hear your feedback.

How useful was this post?

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

One thought to “Style Distributor (PubSub & Messaging Service)”

  • RoyalCBD.com

    Very good blog post. I certainly appreciate this site.
    Keep writing!

    Reply

Leave a comment to RoyalCBD.com Cancel reply

Your email address will not be published. Required fields are marked *