Communication between Lightning Web Components Guide

Here is our third article in the series on LWC. In the previous two, we delved into the principles of building and using components, as well as the basic tenets of markup and its features in LWC. In this article, we will explore communication between LWC components, so let's get started.

As a Lightning web component (LWC) developer, you may need to transfer data between components in cases where components are parent-child or unrelated. In this article, we will look at three ways to transfer data between LWC components:

  • Using the "@api" annotation.

  • Using dispatch event.

  • Using the Lightning message channel message service.

So let's go!

"@api" Annotation Properties

Let's take a look at the "@api" annotation. This annotation is used to define public properties and methods in a component that can be accessed by other components. This is especially useful when transferring data between parent and child components. We can define a public property in the child component using the "@api" annotation and set its value in the parent component. For example, we can define a public property in the child component as follows.

import { LightningElement, api } from 'lwc'; 
export default class ChildComponent extends LightningElement { 
     @api message; 
}

In the parent component, we can set the value of the 'message' property for the child component as follows:

<template> 
     <c-child-component message="Hello World"></c-child-component> 
</template>
In this example, we pass the 'Hello World' value from the parent component to the child component using the '@api' annotation.

Let's also consider the use of '@api' with functions. They allow invoking specific functionality of a component from outside, from other components or templates. This is a powerful tool for communication between components and exchanging data and actions.

You can declare a method in your LWC component and annotate it with '@api'. This allows the method to become public and accessible for invocation from other components.

import { LightningElement, api } from 'lwc'; 
export default class MyComponent extends LightningElement { 
     @api myMethod(param) { 
          console.log('Received parameter:', param); 
     } 
}
In another LWC component, you can reference the component containing a method annotated with "@api" and call that method.
<template> 
     <c-my-component></c-my-component> 
     <lightning-button label="Call Method" onclick={callMethod}></lightning-button> 
</template> 
import { LightningElement } from 'lwc'; 
export default class AnotherComponent extends LightningElement { 
     callMethod() { 
          const myComponent = this.template.querySelector('c-my-component'); 
          if (myComponent) { 
               myComponent.myMethod('Hello from AnotherComponent'); 
               } 
          } 
}

In the example provided above, when the button is pressed on the parent component, the child component receives a specific text as a parameter and outputs it to the console.

The use of "@api" allows you to publicly export the functionality of your component and facilitate interaction with other components. Remember that adhering to security practices and robust design is crucial for effective communication between components.

Dispatch Event

Another way of communication between components is through dispatch events, which are used to transmit data between components that are not necessarily related. They can be used for data exchange between parent and child components, sibling components, or even components that do not belong to the same hierarchy.

Let's consider an example of a simple form where the user enters some data and clicks the 'Submit' button.
We can create a special event in the form component triggered when the user clicks the 'Submit' button. The event can contain the data entered by the user, which can then be passed to another component for further processing. Here is how we can create a special event in the form component:
import { LightningElement } from 'lwc'; 
export default class FormComponent extends LightningElement { 
     handleSubmit() { 
          const data = { name: this.template.querySelector('input[name="name"]').value, email: this.template.querySelector('input[name="email"]').value }; 
          const event = new CustomEvent('submitform', { detail: data }); 
          this.dispatchEvent(event); 
          } 
}

In this example, we create a special event named 'submitform' and pass it to an object containing the user-entered data. Then, we use the dispatchEvent method to trigger the event.

In another component, we can listen for the 'submitform' event and process the received data. Here is how we can listen for the event and handle the data:

import { LightningElement } from 'lwc'; 
export default class ResultComponent extends LightningElement { 
      connectedCallback() { 
           this.addEventListener('submitform', this.handleFormSubmit.bind(this)); 
      } 
      handleFormSubmit(event) { 
           const data = event.detail; 
           console.log(`Name: ${data.name}, 
           Email: ${data.email}`); 
      } 
}

In this example, we use the addEventListener method to listen for the 'submitform' event. When the event is triggered, the 'handleFormSubmit' method is called, and we retrieve the data passed in the event using the 'detail' property. This approach is useful when components do not belong to the same hierarchy.

dont miss out iconDon't forget to check out: LWC Interview Question – Part 2

Another way to handle events is by using the 'on' directive in the HTML file where the child component is invoked.

To capture this event in the parent component using the 'on' directive, you can do the following:

  • In the HTML file of the parent component, add the child component and include the 'on' directive:

<template> 
    <div> 
        <c-my-child-component onsubmitform={handleFormSubmit}></c-my-child-component> 
    </div> 
</template>
  • In the 'on' directive, specify the event name (in this case, onsubmitform) and the name of the function that will handle the event (in this case, handleFormSubmit). In the JavaScript file of the parent component, define the handleFormSubmit function:

handleFormSubmit(event) { 
     const data = event.detail; 
     console.log(`Name: ${data.name}, Email: ${data.email}`); 
}
  • In the handleFormSubmit function, you can access the event properties and perform any necessary actions based on the data in the event.

Note that when using the 'on' directive, the event handling function must be defined in the JavaScript file of the parent component.

One of the advantages of using the 'on' directive is that it allows you to handle events more declaratively. By including the 'on' directive in the HTML file, you can see which events are being handled and which functions handle them, making code understanding and maintenance easier.

However, using the 'on' directive can also make the code less flexible. If you need to handle the same event in multiple components, you will need to define the event-handling function in the JavaScript file for each element. Conversely, if you use event listeners in the JavaScript file of the parent component, you can define the event handling function once and reuse it in multiple components.

In general, both approaches have their advantages and drawbacks, and the choice between them depends on the specific requirements of your project.

Let's also discuss the use of the "bubbles" and "composed" parameters in our dispatchEvent.

Bubbles

The 'bubbles' parameter is used when dispatching events in Lightning (LWC) to control whether the event 'bubbles' through the DOM hierarchy. Event bubbling is a mechanism where, after processing an event on a specific element, it then propagates up to parent elements for possible further handling.

In LWC, events typically bubble by default, similar to standard browser events. This means that if you dispatch an event from a child component, it will travel up through the hierarchy of parent components, allowing higher-level components to listen for the event and respond to it if they choose to do so. This natural event propagation can simplify the communication between components, enabling a parent component to respond to actions taken by its child components.

Composed

The 'composed' parameter is another attribute used when dispatching events in LWC, and it determines whether the event can cross the boundary of the shadow DOM. Shadow DOM is a fundamental concept in web components that encapsulates a component's styles, structure, and functionality.

If the 'composed' parameter is set to true when dispatching an event, then this event can leave the boundaries of the shadow DOM and be processed by elements outside it. This can be useful if you want to communicate between components that are not directly related through the component hierarchy. However, setting the 'composed' parameter to true can also lead to event handling in unexpected places if not used carefully.

On the other hand, if 'composed' is false, the event remains within the bounds of the shadow DOM. This can provide better isolation and control over event handling, restricting interaction between components and preventing event processing outside the designated area.

These parameters, 'bubbles' and 'composed,' provide flexibility in how events propagate and are handled in Lightning web components. Understanding their behavior can help you develop more efficient and effective strategies for communication between your components, promoting modular and maintainable code. For a more detailed understanding of their behavior and recommendations, we recommend referring to the official Salesforce documentation.

Lightning Message Channel

Additionally, for communication between components, there is the Lightning Message Service (LMS) — an event-driven architecture that allows for message exchange with publishing and subscribing, enabling communication between unrelated components. LMS uses a 'message channel' to define communication between components. Components can publish messages on a specific message channel, and other components can subscribe to this channel to receive messages.

Let's consider an example of a shopping cart application where users can add items from multiple components. We can use LMS to communicate between components and update the cart when an item is added or removed.

Here is how we can use LMS to achieve this:
  • First, we need to create a message channel that will be used for communication between components. We can create a message channel using the 'messageChannel' module in LWC.

  • In Visual Studio Code, navigate to 'messageChannels,' create a new file, name it appropriately, and add '.messageChannel-meta.xml' at the end.

  • In the file, place the following XML:

<?xml version="1.0" encoding="UTF-8"?> 
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata"> 
     <masterLabel>cartChannel</masterLabel> 
     <isExposed>true</isExposed> 
     <description>Message Channel for cart updates</description> 
     <lightningMessageFields> 
          <name>cartItems</name> 
          <description>Items in the cart</description> 
          <type>Text</type> 
     </lightningMessageFields> 
</LightningMessageChannel>
  • Next, we need to create a publisher component that will publish messages on the 'cartChannel.' Let's assume we have a product list component where the user can add items to their cart. We can create a special event in the product list component that is triggered when the user adds an item to their cart. The event can contain details of the item added by the user, which can then be passed to the publisher component.

import { LightningElement, wire } from 'lwc'; 
import { publish, MessageContext } from 'lightning/messageService'; 
     import CART_CHANNEL from '@salesforce/messageChannel/cartChannel__c'; 
     export default class ProductListComponent extends LightningElement { 
          @wire(MessageContext) messageContext; handleAddToCart(event) { 
               const data = { name: event.detail.name, price: event.detail.price }; 
               const message = { data: data }; 
               publish(this.messageContext, CART_CHANNEL, message); 
               } 
     }

In this example, we use the 'publish' method to publish a message on the 'cartChannel.' We pass the message context, message channel, and message itself as parameters to the 'publish' method.

Finally, we need to create a subscriber component that will listen for messages on the 'cartChannel' and update the cart accordingly.

import { LightningElement, wire } from 'lwc'; 
import { subscribe, MessageContext, unsubscribe } from 'lightning/messageService'; 
import CART_CHANNEL from '@salesforce/messageChannel/cartChannel__c'; 
export default class CartComponent extends LightningElement { 
     @wire(MessageContext) messageContext; 
     subscription; 
     connectedCallback() { 
          // Subscribe to the CART_CHANNEL this.subscription = subscribe( this.messageContext, CART_CHANNEL, (message) => { this.handleMessage(message); 
          } 
          ); 
     } 
handleMessage(message) { 
     // Extract and handle data from the message const data = message.data; 
     // Update the cart with the data received from the message 
     } 
disconnectedCallback() { 
     // Unsubscribe from the message channel when the component is disconnected unsubscribe(this.subscription); 
     } 
}
In this example, we use the 'subscribe' method to subscribe to the 'cartChannel' and listen for messages. When a message is received, the 'handleMessage' method is called, and we extract the data from the message using the 'data' property.

That's it! We have used LMS to communicate between unrelated components and update the cart each time an item is added or removed. Going forward, we can create more components that publish messages on the channel with information about adding products to the cart and using the same channel.

In conclusion, LMS can be a resource-intensive method of communication, and its usage should be avoided where possible. 

For example, the use of the "@api" annotation is ideal for passing data between parent and child components, as it offers a direct way to access the properties and methods of the child component.

dont miss out iconCheck out another amazing blog by Sparkybit here: Salesforce User Stories: Salesforce Adoption Experience of FinTech Leaders Globally

On the other hand, events are useful for communication in the reverse direction, from child components to parent components. Additionally, with event listeners, we can simultaneously notify multiple components about changes in the state of our component.

The Lightning Message Service is recommended for data exchange between unrelated components that do not belong to the same hierarchy. It is also useful when a component needs to send a message to multiple components subscribed to the same message channel.

Lightning Web Components Developer Guide Part 3: To sum up

Carefully consider the use case and choose the appropriate communication method to ensure optimal performance and efficient data transmission between components.

So, if you are a Salesforce developer and have not yet explored the LWC framework, we strongly recommend giving it a try. With its powerful features and ease of use, it quickly becomes the preferred framework for building Lightning web components on the Salesforce platform.

In the next, final article of the series, we plan to delve into the communication of components with Salesforce and Apex, exploring their methods, advantages, disadvantages, and providing recommendations for their application.

Sparkybit, Salesforce consulting and development company. We make Salesforce perform at its best by fitting it to Clients’ custom business needs. 9 years of sharpened expertise, 25+ successful long-term projects globally.

[email protected]

Copyright belongs to Sparkybit GmbH, Im Dörener Feld 3, 33100, Paderborn, Germany.
2023 © Sparkybit. All rights reserved.

Responses

Popular Salesforce Blogs