Demystifying Application and Component Events in the Lightning Component Framework

Mehdi Maujood
Salesforce Zolo
Published in
13 min readJan 25, 2019

Lightning components and applications communicate with each other primarily through an event-based model powered by Component Events and Application Events. However, reading the official developer documentation for the first time left me with some unanswered questions. I had to build out a few applications and test them out myself to learn exactly how these events function.

In this post, we’re going to do just that — build lightning components and see in detail how events function, which components can receive what events, the order in which they are received by other components and explore options such as bubble, capture and stopPropagation(). Last, we’ll also have a look at component methods as a means of inter-component communication.

Application Events

Custom lightning applications are generally structured as a parent application containing a hierarchy of lightning components. The defining characteristic of Application Events is that if any component in the hierarchy (or even the application) fires the event, every other component (or the application) will get a chance to handle the event. Think of them as “broadcast” style events — one component can broadcast a message to anyone who’s willing to listen.

Before discussing how Application Events work, let’s do a quick review of how to create them.

Create the Event

Create the Application Event by selecting File > New > Lightning Event in the Developer Console and add the following code:

<aura:event type="APPLICATION" description="Event template">
<aura:attribute name="message" type="String" />
</aura:event>

Save it as LightningSimpleEvent. Notice the <attribute> tag — lightning events can not only notify receivers, they can also carry data. You can add multiple attributes here.

Fire the Event

In the component that needs to fire the event, add the following lines as part of the markup:

<aura:registerEvent name="appEvent" type="c:LightningSimpleEvent"/>

Notice the type attribute — it should contain the name you saved your event with. Next, in the controller:

var appEvent = $A.get("e.c:LightningSimpleEvent");
appEvent.setParams({ "message" : "Special Message" });
appEvent.fire();

Notice that when using $A.get to obtain the event, we don’t use appEvent which is the name we used when registering the event. Instead, we use the name that we saved the event file with. The name property on the registerEvent tag doesn’t really have much use for us — it’s the type property that matters.

Handle the Event

For the component handling the event, we need to add the following to the component markup:

<aura:handler event="c:LightningSimpleEvent" action="{!c.handleApplicationEvent}"/>

And we also add a method called handleApplicationEvent in the controller:

handleApplicationEvent : function (component, event, helper) {
var message = event.getParam("message");
component.set("v.receivedMessage", message);
}

Application Events in Default Phase

I created a small application to observe how Application Events work. It looks like this:

We have an application that contains two components. The two components, in turn, contain two more components to a create a total six components inside an application. Each component (as well as the application) has the ability to fire the application event we defined above (LightningSimpleEvent) through a button click, and each component also handles the same event and logs to the console when the event is handled.

What happens when we click the Send Event button on Application 1:0? We see the following in the console:

As you can see, the two innermost components 43:0 and 70:0 got to handle the event first and then their parent 22:0. Next, the innermost components 124:0 and 151:0 followed by their parent 103:0. Finally the top level application gets to handle the event.

What happens if I click one of the innermost buttons?

Exactly the same. It doesn’t matter where the event came from in the hierarchy. All components will get a chance to handle it.

The official documentation says that default application events are handled in a non-deterministic order, which just means we should not rely on the order in which the handlers receive the event. That does make sense because if you look at the order, it doesn’t look like something that you would want to rely on. If you have some background in data structures and algorithms, you might notice that the order is in fact a post-order traversal of the component hierarchy, but that very well might be an implementation detail that could change in the future.

Application Events in Bubble Phase

The example we saw above was handling events in “default” phase. When placing a handler, you have the option to handle the event in the “bubble” phase:

<aura:handler event="c:LightningSimpleEvent" action="{!c.handleApplicationEvent}" phase="bubble"/>

Before discussing what the bubble phase is, let’s change all the handlers in our example application to have phase="bubble". When I click 70:0 while all the handlers are handling in the bubble phase, I see the following in the console:

As you can see, in the bubble phase the event was first handled by the innermost component, the one that was clicked. Next, the parent got to handle the event and then the parent’s parent.

Clicking 22:0 does something similar — firing component handles first and then we go all the way up to the root:

The workings of the bubble phase in application events should be clear from these experiments. The element that fires an event gets a chance to handle it first, its parent element second, grandparent third and so on until the application root gets to handle the event.

Application Events in Capture Phase

We’ll start by doing exactly what we’ve been doing so far: code an example. For the next experiment, I changed all my event handlers to have phase="capture" instead of phase="bubble". Let’s observe what happens when we send an event from 70:0:

As you can see, when the components were handling the event in the “capture” phase, the application root got to handle the event first, and then the event traveled down to the component that actually fired it. The component that fired the event got to handle it last.

If the event was fired by 22:0, the application root would get to handle it in the capture phase first, 22:0 would get to handle it second, and no other components would be able to handle the event during the capture phase:

Application Events Lifecycle

Now that we have a good grasp of what the three different phases look like, let’s go over a summary of the flow that application events follow:

  • Whenever an application event is fired, the capture phase is the first to begin.
  • The event is first “captured” by the outermost component, the application root. If the application root has a phase="capture" handler attached to it, it will be able to handle the event.
  • Once the application root has handled the event, the event will continue travelling down to the component that fired it and will be handled the next component in line — the child of the application root that leads to the target component. If the child has a phase="capture" handler, it will be able to handle the event.
  • The event continues to flow down until it reaches the component that fired the event. Since the event has now reached the target, the “capture” phase is complete.
  • The bubble phase begins right after capture is complete. In the bubble phase, the event is first received by the component that fired it. If the component has a handler for the event with phase="bubble", it will get a chance to handle it.
  • Next, the event “bubbles” to the parent. If the parent has a phase="bubble" handler, the parent gets to handle it.
  • The “bubbling” continues until the event has bubbled to the application root where the event can be handled with a phase="bubble" handler. You can probably see by now why this phase is called “bubble” — the event is rising up just like a bubble in water rises to the top.
  • Once the bubble and capture phases are complete, the third phase is the default phase. In this phase, like we saw earlier, all components as well as the root get a chance to handle the event.

The above lifecycle is broken if we use stopPropagation() somewhere in the capture or bubble phases, but we will examine that later. For our next experiment, we’ll look at component events.

Component Events

The defining characteristic of component events is that they only support capture and bubble phases. There is no default phase. What this implies is that if a component event is fired by a component, only components in the containment hierarchy will be able to handle the event, i.e. only parents, grandparents and the application root will be able to handle a component event.

You might wonder then, and rightfully so, why use component events at all? Application events can evidently do more. Application events fired by a component can be handled by the containment hierarchy if we want, but we have the additional option of having children and other sub-trees handle the event too.

There are two very good reasons to use component events whenever possible. The first reason is that component events are more efficient. The second (and the more compelling) reason is that component events facilitate better design by encouraging the principle of least knowledge. In an ideal world, a component should only talk to components that it has a direct relationship with, such as its children or its parent. If you find yourself in a position where you need to communicate with a component in another sub-tree, consider if sending a component event to the parent and having the parent communicate with the child in question is a better design choice. Application events should only be reserved for cases where the event truly needs to be an application-level event.

You might also wonder, and again rightfully so, that if component events can only be handled by the containment hierarchy and application events are to be avoided unless necessary, how does a component send a message to a child? There are ways to do that, but the recommended approach is not through events. We will get back to this once we’re done with events.

Creating Component Events

Just like application events, component events need to be defined in an event file that looks like this:

<aura:event type="COMPONENT" description="Event template">
<aura:attribute name="message" type="String" />
</aura:event>

The only difference from an application event definition, highlighted in bold, is the type attribute which now has a value "COMPONENT".

Firing Component Events

Just like application events, a component event needs to be registered in the component markup:

<aura:registerEvent name="compEvent" type="c:LightningComponentEvent"></aura:registerEvent>

This is exactly how application events are registered too. Remember when we talked about application events, we discussed that the name property is not useful for us? In case of component events, the name property is pretty important.

The firing component also needs the following code to fire the event:

var evt = component.getEvent('compEvent');
evt.fire();

You can see that instead of using the event file name (c:LightningComponentEvent) we’re using the value we had set in the name attribute. Also, we’re using component.get instead of $A.get.

Handling Component Events

Just like application event handlers, a component that handles a component event must define a handler in the markup and an associated function in the controller:

<aura:handler name="compEvent" event="c:LightningComponentEvent" action="{!c.handleEvent}"></aura:handler>

Notice the one thing that wasn’t there in our application event handler — the name attribute. In case of component events, a name attribute needs to be present and it must match the name attribute on the aura:registerEvent tag for the event that needs to be handled. In our example, both the aura:registerEvent and aura:handler tags have name="compEvent".

The phase attribute is optional — the framework will handle events in the bubble phase by default. To handle component events propagating in the capture phase, phase="capture" needs to added to the aura:handler.

Capture and Bubble Phases in Component Events

Like we discussed, capture and bubble phases in component events work just like they work in application events. We don’t need to go over the experiments again, but just for the sake of seeing how they work and for setting the stage for understanding the next topic, we’re going to do a little experiment. I wrote a similar application to the one we used last time, but this time using component events. For this experiment, I added two handlers to each component. One handling the event in the bubble phase, and one in the capture phase:

<aura:handler name="compEvent" event="c:LightningComponentEvent" action="{!c.handleEventCapture}" phase="capture"></aura:handler><aura:handler name="compEvent" event="c:LightningComponentEvent" action="{!c.handleEventBubble}" phase="bubble"></aura:handler>

Let’s see what happens when we click one of the innermost components:

You can see very clearly how the event was captured by the component and how it bubbled back to application root. If we click 22:0:

We see the following in the console:

As expected, the parents all get the event in both phases, but there is no way for either the children or other sub-trees to handle the event.

stopPropagation()

Stop propagation has a very simple job: Stop the capture and bubble process wherever it is. For an experiment, I used the following event handler in the component 22:0

<aura:handler name="compEvent" event="c:LightningComponentEvent" action="{!c.handleEventBubble}" phase="bubble"></aura:handler>

I modified the action associated with this handler to stop the propagation of events:

handleEventBubble: function (component, event, helper) {
console.log('event in bubble phase received by outer controller: '
+ component.getGlobalId());
event.stopPropagation();
}

Whenever component 22:0 handles compEvent in the bubble phase, the propagation would stop and no other components will receive the event. Let’s send the event from component 70:0 and see what happens:

As you can see, the capture phase was completed successfully, but the bubble phase was interrupted and the event never bubbled to the application root.

What happens if we call event.stopPropagation() in the capture handler in component 22:0? Let’s have a look:

The capture phase began with the application root handling the event, continued with the component 22:0 handling the event but the cycle was stopped because the handler in component 22:0 called the stopPropagation() method. The capture phase was never completed and the bubble phase never started.

stopPropagation() in Application Events

Application Events here require a special mention because of two reasons.

First, stopPropagation() does nothing if it is called in the default phase. The first component to handle the event in the default phase could call stopPropagation() and all other components would still get a chance to handle the event.

Second, if stopPropagation() is called during the bubble or capture phases of an application event, the propagation stops just as expected. However, the default phase still executes — with one big difference: the component that called stopPropagation() will now be treated as the root for the default phase and only that component along with all its descendants will get to handle the event.

Communicating with Child Components

Although this article is about events, it’s important to talk about how parent components communicate with child components as events are all about communicating between components. We’ve learned so far that component events are the method of choice for sending events that should be handled in the containment hierarchy. We’ve also learned that application events should be avoided unless the event is genuinely an application-level event that all components might need to hear about. So how does a parent component send a message to a child component?

Sending Data through Attributes

The simplest method is to pass data to child components through attributes. Suppose you have a child component that looks like this:

<aura:component>
<aura:attribute name="fullName" type="String"/>
</aura:component>

The component or application that includes this component can control the value of the attribute in the following way:

<aura:application>
<aura:attribute name="selectedName" type="String"/>
<c:ChildComponent fullName="{!v.selectedName}"/>
</aura:application>

Notice how the attribute in the child is set to the value of an attribute on the parent. This value is bound to the child attribute by reference, which means that any time the parent sets a new value in the selectedName attribute, the fullName attribute in the child component is updated too. Conversely, any time the child updates the value of fullName, the value in the parent is updated as well.

Defining methods on child components

The more powerful way of having parents communicate with child components is through defining methods on the child that can accessed by the parent. A method in a child component is defined using something like the following:

<aura:method name="renderAccounts" action="{!c.renderAccounts}"
description="Take a list of accounts and render them">
<aura:attribute name="accounts" type="Account[]"/>
<aura:attribute name="searchTerm" type="String" />
</aura:method>

As you might guess, the method will require an associated renderAccounts function in the controller.

The parent component, in order to call this method, will have to first obtain a reference to the child component and then call the method on it using something like the following:

var childCmp = component.find('cmpResults');
childCmp.renderAccounts(accounts, searchTerm);

You can use aura:methods to create something of an API that allows parents to communicate with the child.

Component events, application events and component methods provide us with a comprehensive framework for creating reusable and decoupled lightning components.

Summary

Component events together with methods provide a great way for closely-related components to interact with each other and should be the methods of choice for communication between components. Application events give us a way to create “broadcast” style events which should be used to create events that any component in the application might need access to.

For further reading, check out the official developer documentation for events and the docs for creating methods in components. Also, here’s another great article about communication patterns in lightning applications.

Thank you for reading, and if you enjoyed it please do let me know!

--

--

Mehdi Maujood
Salesforce Zolo

Software engineer, Salesforce enthusiast, Pluralsight author