Monday, May 14, 2018

Lightning Inter-Component Communication Patterns

If you’re comfortable with how a Lightning Component works and want to build production-grade applications for use in your org or to sell in AppExchange, this article is a must read. Understanding how a singular component works is important, but understanding how they work together is essential for building an effective application.
Interactive applications require components that can exchange data. In traditional HTML and JavaScript, this is straightforward as any script can modify the whole page. The modular nature of the Lightning Component Framework (LCF) requires more consideration for interactivity.
In line with best practices for security concerns, Lightning components are intentionally initially isolated from each other. By default, they’re safe from receiving or causing unwanted interference that can be exploited for malicious purposes. In practice, a Lightning component’s code cannot directly interact with its parent and vice versa. A parent component cannot manipulate its children or siblings as with standard JavaScript and the DOM. Inter-component communication must be specified by the developer.

In LCF, inter-component communication is supported in several well-defined ways. One can only use the following developer-defined interfaces to specify what can be exchanged:
  • Attributes or Methods to pass data down the component hierarchy
  • Lightning Events to pass data up and around in the component hierarchy

Passing data down the component hierarchy

Attributes

Attributes are the most commonly used element to pass data down the component hierarchy as they are simple to use. In order to pass data down from a parent component to its child, simply use the following code:
Parent component
1<aura:component>
2   <aura:attribute name="parentAttribute" type="String"/>
3   <c:childComponent childAttribute="{!v.parentAttribute}"/>
4</aura:component>
Child component
1<aura:component>
2   <aura:attribute name="childAttribute" type="String"/>
3</aura:component>
In this example, the parent component value of parentAttribute is transferred to the childAttribute of the child component via the {!v.parentAttribute} expression.
This is perfect if you just want to display the data in a child component. What about if you also want to execute some logic when the attribute’s value changes?
Consider the following updated definition of childComponent :
1<aura:component>
2   <aura:attribute name="childAttribute" type="String"/>
3   <aura:handler name="change" value="{!v.childAttribute}" action="{!c.onChildAttributeChange}"/>
4</aura:component>
With the addition of a change handler, the child component can now trigger the onChildAttributeChangecontroller function automatically when the value of childAttribute changes. This allows us to implement some custom logic such as:
1({
2    onChildAttributeChange : function (component, event, helper) {
3        console.log("Old value: " + event.getParam("oldValue"));
4        console.log("Current value: " + event.getParam("value"));
5    }
6})
We now have established a top-down communication chain between the parent and the child component. This can be summarized in these few steps:
  1. parentAttribute value changes
  2. parentAttribute value is transferred to childAttribute
  3. childComponent’s change handler triggers the onChildAttributeChange controller function
This approach works great for processing an attribute. What about multiple attribute changes? If you want to change two or more attributes and then trigger some logic, this method becomes unwieldy. You can either combine the attributes into a larger object (not always practical) or write a complex synchronization algorithm (please don’t). Instead, I recommend methods for multiple attribute changes.

Methods

Based on frequent exchanges with the developer community, I have gathered that methods tend to be overlooked in favor of attributes. However, I have found methods to be quite flexible, as they allow users to create and expose component APIs.
Let’s look at an example involving two components communicating with a method. Here we have a child component that exposes a myMethod method with two parameters (param1 and param2).
1<aura:component>
2    <aura:method name="myMethod" action="{!c.executeMyMethod}">
3        <aura:attribute name="param1" type="String"/>
4        <aura:attribute name="param2" type="String"/>
5    </aura:method>
6</aura:component>
myMethod is hooked to an executeMyMethod function in the component’s controller:
1({
2    executeMyMethod : function (component, event, helper) {
3        var params = event.getParam('arguments');
4        console.log('Param 1: '+ params.param1);
5        console.log('Param 2: '+ params.param2);
6    }
7})
This function retrieves the arguments (param1 and param2) passed to myMethod and outputs them in the console. Note that the arguments key used in event.getParam is a constant.
Let’s now look at the parent component. It has two attributes (parentAttribute1 and parentAttribute2), a reference to the child component, and a button.
1<aura:component>
2    <aura:attribute name="parentAttribute1" type="String" default="A"/>
3    <aura:attribute name="parentAttribute2" type="String" default="B"/>
4     
5    <c:childComponent aura:id="child"/>
6     
7    <lightning:button label="Call child method" onclick="{! c.onCallChildMethod }" />
8</aura:component>
When clicked, the button calls a onCallChildMethod function in the component’s controller. This function retrieves the value of the two attributes and retrieves the child component by using its aura:id. It then calls a myMethodmethod on the child component and passes the two attribute values as parameters.
1({
2    onCallChildMethod : function(component, event, helper) {
3        var attribute1 = component.get('v.parentAttribute1');
4        var attribute2 = component.get('v.parentAttribute2');
5        var childComponent = component.find('child');
6        childComponent.myMethod(attribute1, attribute2);
7    }
8})
If we now step back and look at the big picture, here’s what happens:
  1. When the parent component button is clicked, the onCallChildMethod controller function of parentComponent is called
  2. onCallChildMethod retrieves a reference to childComponent using find with an aura:id
  3. onCallChildMethod calls the myMethod method of childComponent
  4. myMethod triggers the executeMyMethod controller function of childComponent
This “method” approach is quite powerful as users can pass data to a child component and perform some operations once this is done. Users can also create distinct methods involving the same arguments but triggering different functions. Finally, developers get the benefit of clarity by exposing named methods that—hopefully—reflect their intended behavior.
Achieving all of this is not possible by just passing attributes from parent to child components.

Passing data up and around the Lightning component hierarchy

The way to pass data up and around in the Lightning component hierarchy is to use events. There are two types of events that users can employ for that purpose: application events and component events.
There are some minor syntax differences between these two types of events, but we do not discuss them in this article for the sake of brevity. Instead, we focus on their propagation mechanisms, which in turn dictates their use cases.

Application Events

Application events are broadcast to all Lightning components that are registered as listeners for that specific event.
If we look at the example described in the schema on the right, here’s what happens:
  1. A component fires an application event.
  2. All other components can handle the event provided that they have registered the appropriate event handler.
All event handlers are triggered simultaneously. There is no way to cancel an application event once fired.
Application events are great for supporting business logic events as they are quite flexible: They do not impose a particular architecture. This is ideal when building components that are exposed in the Lightning App Builder. However, bear in mind that this flexibility comes at the expense of performance in certain use cases due to the event broadcast.
For example, it can be expensive to use an application event for a fine-grained component such as a button to notify other components that it is clicked. Your event will be sent to all of the components. They have to identify the source of the event then, verify if they handle it. Typically all components except one are registered to handle the event. Conversely, if you use an application event for a coarse-grained event in the App Builder that two other components may listen to, there is no performance impact.

Component Events

Component events are “clones” of standard DOM events (mouse clicks, key press, and so on). Just like their DOM counterparts, they propagate up in the component hierarchy via a bubbling mechanism and can be stopped en route to the application root component.
Here is an example of such a behavior:
  1. Component E fires a component event.
  2. Event bubbles to E’s direct parent: component D.
  3. Component D can handle the event or not and optionally prevent its propagation by capturing it.
  4. If Component D did not capture the event, it propagates to A (this applies even if D did not handle the event).
  5. Component B and C do not handle the event, as they are not in the ancestry line of E.
The advantage of component events is that you know their maximum scope in advance (all parent components) and you have some degree of control over it (you can capture the event along the way).

Advanced event architecture

As a rule, consider using a component event before employing an application event. These are more common and usually have little effect on performance. However, when facing a blocking use case or an overly complex architecture, think about going for an application event.
Consider using a component event for handling low-level UI interactions such as selections and form validation. You can then combine these with application events that handle “business” events. This integrates into a larger architecture via a central “dispatcher” component such as this:

Closing words

In this article we covered Lightning inter-component communication options. You learned about passing data down the component hierarchy with attributes and methods. You also had an overview of the different event types with their use cases and limitations. You are now ready to build a larger Lightning project with a robust architecture that you can quickly deploy to production. If you have any questions, reach out to our community’s Stack Exchange.

Code samples

Here are some working code samples covering the inter-component communication patterns presented in this post:

No comments:

Post a Comment