Being able to invoke methods and pass data between parent and child components is critical when building an application structure that is both extensible and easy to use.
One of the basic features of Lightning Aura components is the ability to nest components (components containing other components). Communicating from a Parent Component to a Child Component is easy with Aura.
The Child Component exposes an Aura:Method
…
<aura:component>
<aura:method name=”getData” action=”{!c.getData}” />
</aura:component>
getData: function(component, event, helper) {
return helper.getData(component);
}
getData: function(component) {
// return some data
return Math.random();
}
… and from the Parent Component, we simply get a handle on the Child Component and invoke that method.
<aura:component>
<!-- parent component contains an instance of ChildComponent -->
<c:ChildComponent aura:id=”myChildComponent” />
</aura:component>
invokeChildMethod: function(component) {
// find the child component
const myChildComponent = component.find(“myChildComponent”);
// invoke a method on the child component
const response = myChildComponent.getData();
}
.. and it’s that easy.
Unfortunately, initiating communications from a Child Component to a Parent Component is a bit more cumbersome. Salesforce offers up two solutions to accomplish this - both of which leave something to be desired. (Skip right to the best solution)
Aura.Action
Aura allows us to define an attribute on a Child Component which is of type Aura.Action
. A callback method can be bound to this attribute from the Parent Component.
The Child Component can invoke the method that was bound to it’s Aura.Action type attribute whenever it’s appropriate.
<aura:component>
<aura:attribute name=”myInputString” type=”String” default=”” />
<aura:attribute name=”onDataReady” type=”Aura.Action” />
<button onclick=”{!c.sendData}”>Send Data</button>
</aura:component>
sendData: function(component, event, helper) {
helper.sendData(component);
}
sendData: function(component) {
const callbackMethod = component.get(“v.onDataReady”);
callbackMethod.setParams({ “data”: component.get(“v.myInputString”) });
$A.enqueueAction(callbackMethod);
}
<aura:component>
<c:ChildComponent onDataReady=”{!c.onDataReady}” />
</aura:component>
onDataReady: function(component, event, helper) {
const args = event.getParams();
helper.onDataReady(component, args.data);
}
onDataReady: function(component, data) {
console.log(data);
// do something with response
}
… so when the Send Data button is clicked in the Child Component, the sendData
method is invoked, which inturn invokes the onDataReady
attribute’s action.
The problem is that Salesforce no longer advises this method as of API 41. Why? For a variety of reasons.
Component Events
Salesforce recommends using events to communicate between your components.
I have always found this approach to be cumbersome and overly complicated.
First you have to create a new Aura:Event
component.
<aura:event type=”COMPONENT”>
<aura:attribute name=”data” type=”string” />
</aura:event>
Next you have to setup the Child Component to work with the event. This involves registering the event, and firing it when appropriate.
<aura:component>
<aura:attribute name=”myInputString” type=”String” default=”” />
<aura:registerEvent name=”onDataReady” type=”c:DataCallbackEvent” />
<button onclick=”{!c.sendData}”>Send Data</button>
</aura:component>
sendData: function(component, event, helper) {
helper.sendData(component);
}
sendData: function(component) {
const onDataReadyEvent = component.getEvent(“onDataReady”);
callbackMethod.setParam(“data”: component.get(“v.myInputString”));
onDataReadyEvent.fire();
}
Finally, you setup the Parent Component to listen for the event and invoke the proper method when the event arrives.
<aura:component>
<aura:eventHandler event=”c:DataCallbackEvent” action=”{!c.onDataReady}” />
<c:ChildComponent />
</aura:component>
onDataReady: function(component, event, helper) {
const args = event.getParams();
helper.onDataReady(component, args.data);
}
onDataReady: function(component, data) {
console.log(data);
// do something with response
}
Aside from simply feeling cumbersome, there are several issues with this approach. First, managing event propagation can be tricky and confusing. Second, there is no direct 1:1 relationship between your Child Component and the callback in the Parent Component. The moment that you want to use multiple instances of your Child Component within the same Parent Component - and have each Child Component communicate with the Parent Component differently you run into a problem.
<aura:component>
<aura:eventHandler event=”c:DataCallbackEvent” action=”{!c.onDataReady}” />
<c:ChildComponent /> <!-- child component 1 -->
<c:ChildComponent /> <!-- child component 2 -->
</aura:component>
onDataReady: function(component, event, helper) {
const args = event.getParams();
helper.onDataReady(component, args.data);
}
onDataReady: function(component, data) {
console.log(data);
// do something with response
}
.. regardless of which Child Component fires the DataCallbackEvent
event it will be handled the same way in the Parent Component.
You can hack your way around the issue by extending the Event Component and packing additional data into it, but this is just treating a symptom and not the problem.
So what do we do?
Direct Events
Wouldn’t it be great if we could combine the simplicity of the Aura.Action approach with the platform support of Aura Events? Well, step right up and collect your prize, because today we’re all winners!
The approach here is simple.
First, we define a generic Aura Component Event.
We only need to do this once in our entire application as this event will be reused everywhere that we utilize this pattern.
The event contains a single Map
type attribute, data
, which can be used to pack up any data we like.
<aura:event type=”COMPONENT”>
<aura:attribute name=”data” type=”Map” />
</aura:event>
Next, we rig up the Child Component to fire this event whenever we want. This looks almost exactly like what we did for the Child Component in the previous approach.
<aura:component>
<aura:attribute name=”myInputString” type=”String” default=”” />
<aura:registerEvent name=”onDataReady” type=”c:ComponentEvent” />
<button onclick=”{!c.sendData}”>Send Data</button>
</aura:component>
sendData: function(component, event, helper) {
helper.sendData(component);
}
sendData: function(component) {
const onDataReadyEvent = component.getEvent(“onDataReady”);
// pack in anything we want into the "data" object - the sky is the limit!
callbackMethod.setParam(“data”: {
“value”: component.get(“v.myInputString”)
});
onDataReadyEvent.fire();
}
Finally, we simply connect each Child Component’s event that is fired to the callback that we want.
<aura:component>
<!-- child component 1 uses the receiveFirstName callback -->
<c:ChildComponent onDataReady=”{!c.receiveFirstName}” />
<!-- child component 2 uses the receiveLastName callback -->
<c:ChildComponent onDataReady=”{!c.receiveLastName}” />
<!-- child component 3, event not handled -->
<c:ChildComponent />
</aura:component>
receiveFirstName: function(component, event, helper) {
const data = event.getParam(“data”);
helper.receiveFirstName(component, data.value);
}
receiveLastName: function(component, event, helper) {
const data = event.getParam(“data”);
helper.receiveLastName(component, data.value);
}
receiveFirstName: function(component, value) {
console.log(value);
// do something with response
}
receiveLastName: function(component, value) {
console.log(value);
// do something with response
}
Now you have a direct 1:1 relationship between each Child Component and the callback invoked.
The Parent Component also becomes slightly easier to manage as you no longer have to register for any event you want to handle.
This code is also easier to understand now that the component (c:ChildComponent
), event (onDataReady
), and callback (c.receiveFirstName
) are all grouped together.
<c:ChildComponent onDataReady=”{!c.receiveFirstName}” />
Lightning Web Components
So you’re feeling fancy because you’ve moved on to Lightning Web Components, but you still need to know the best way to manage Child to Parent communication? Luckily, Salesforce has made it even easier than my Direct Event approach… but that’s a topic for another day.