Lightning web components dispatch standard DOM events. Components can also
create and dispatch custom events. Events in Lightning web components are
built on DOM Events, a collection of APIs and objects available in every browser. To communicate
up the component containment hierarchy in Lightning Web Components, we
have to implement the custom events.
Creating an event takes following programming design pattern:
- An event name, called a type
-
A configuration to initialize the event
-
A JavaScript object that emits the event
At the same time one also has to keep in mind while naming the event that
-
No uppercase letters are allowed
-
No spaces are allowed
-
Use underscores to separate words
If you are novice in creating the component events, please
visit Create and Dispatch Events module of lightning web component document library.
Now consider you have built an application which uses the component
hierarchy modal and now you want to communicate up the hierarchy. Well, to
understand this let's take an example. We have following component
hierarchy.
|
Component Hierarchy
|
Now suppose the Grand Child wants to notify of its
behavior change to its parents in the hierarchy. What are
the possible ways.
-
Simply Grand child fires an event and it will be listened by
the parents in the components markup hierarchy - Answer to this is BIG NO Wondered why?
LWC is all about shadow DOM and one can't simply cross/leak through
DOM unless otherwise is authorized to do so.
So how to create and dispatch event from Grand Child which will
propagate through the DOM up to its root i.e. Grand Parent? Let's
crack on it.
-
Configure Event Propagation - Once an event is fired by
Grand Child, it can propagate up through the DOM but when and
where this event can be handled, we have to understand the event
propagation.
Configure Event Propagation
Whenever an event is bubbled up, it becomes part of your component's API
and every consumer along the event's path must understand the event. It’s
important to understand how bubbling works so you can choose the most
restrictive bubbling configuration that works for your component. Lightning
web components support only the
BUBBLING up of events and
NOT CAPTURE phase. Simply think
of the event’s path as starting with your component and then moving to its
parent, and then grandparent, and so on.
An event, inside the component or more specifically inside the shadow tree,
is bound with the target itself. For eg, if we look at the event fired from
onchange of lightning-input or onclick of
lightning-button in Grand Child, we can refer to the target that
fired the event and get the property values with
Event.target but
what if we are firing an event from Grand Child and if it is handled by
Child or Parent or Grand Parent, is it still true to get the target who fired the event?
Well, the answer is again NO. Why?
Well, when an event is handled by another component or let's say from
outside the shadow boundary, the value of Event.target
changes to match the scope of the listener. This change is called Event Retargeting. The event is retargeted so the listener can’t see into the shadow DOM
of the component that dispatched the event. Event retargeting preserves
shadow DOM encapsulation.
Getting too much complicated. Let's come back to our main agenda i.e. event
propagation and you will be all sorted. The propagation depends on two
properties of event
-
bubbles: Indicates whether an event can bubble up the DOM or not
defaulted to false.
-
composed: Indicates whether an event can pass through the SHADOW
boundary or not defaulted to false.
Phew!! we have been through a lot with the event and propagation concept,
let's dive into some examples to understand the propagation behavior more
practically.
Consider our component structure outlined in the above
Component Hierarchy image.
Grand Child Component: This has a
lightning button which fires an event called notify and
other component in the hierarchy up the order wants to listen to this event.
grandChild.html
| <template> |
| <lightning-layout class="slds-var-m-around_medium"> |
| <lightning-layout-item class="wide"> |
| You're in grand child component. |
| <lightning-button label="Hit Me On Grand Child" variant="brand" onclick={handleClick}></lightning-button> |
| <br /><span class="slds-text-color_success">{text}</span> |
| </lightning-layout-item> |
| </lightning-layout> |
| </template> |
grandChild.js
| import { LightningElement } from 'lwc'; |
| |
| export default class GrandChild extends LightningElement { |
| text; |
| |
| handleClick(event) { |
| event.preventDefault(); |
| this.text = 'Notify event fired in grand child.'; |
| |
| const customEvent = new CustomEvent('notify'); |
| |
| console.log(this.text); |
| this.dispatchEvent(customEvent); |
| } |
| } |
Child Component: This component is container for
Grand Child component and listens to the event notify.
child.html
| <template> |
| <lightning-layout class="slds-var-m-around_medium"> |
| <lightning-layout-item class="wide"> |
| You're in child component. |
| <c-grand-child onnotify={handleNotify}></c-grand-child> |
| <span class="slds-text-color_success">{text}</span> |
| </lightning-layout-item> |
| </lightning-layout> |
| </template> |
child.js
| import { LightningElement } from 'lwc'; |
| |
| export default class Child extends LightningElement { |
| text; |
| |
| handleNotify(event) { |
| event.preventDefault(); |
| this.text = 'Notify event listened in child.'; |
| console.log(this.text); |
| } |
| } |
Parent Component: This component is container for Child component and also listens to the event notify.
parent.html
| <template> |
| <lightning-layout class="slds-var-m-around_medium"> |
| <lightning-layout-item class="wide"> |
| You're in parent component. |
| <c-grand-child onnotify={handleNotify}></c-grand-child> |
| <span class="slds-text-color_success">{text}</span> |
| </lightning-layout-item> |
| </lightning-layout> |
| </template> |
parent.js
| import { LightningElement } from 'lwc'; |
| |
| export default class Parent extends LightningElement { |
| text; |
| |
| handleNotify(event) { |
| event.preventDefault(); |
| this.text = 'Notify event listened in parent.'; |
| console.log(this.text); |
| } |
| } |
Grand Parent Component: This is the root container in
the component hierarchy and also listens to the event notify.
grandParent.html
| <template> |
| <lightning-layout class="slds-var-m-around_medium"> |
| <lightning-layout-item class="wide"> |
| You're in grand parent component. |
| <c-grand-child onnotify={handleNotify}></c-grand-child> |
| <span class="slds-text-color_success">{text}</span> |
| </lightning-layout-item> |
| </lightning-layout> |
| </template> |
grandParent.js
| import { LightningElement } from 'lwc'; |
| |
| export default class GrandParent extends LightningElement { |
| text; |
| |
| handleNotify(event) { |
| event.preventDefault(); |
| this.text = 'Notify event listened in grand parent.'; |
| console.log(this.text); |
| } |
| } |
grandParent.js-meta.xml
| <?xml version="1.0" encoding="UTF-8"?> |
| <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> |
| <apiVersion>51.0</apiVersion> |
| <isExposed>true</isExposed> |
| <targets> |
| <target>lightning__HomePage</target> |
| </targets> |
| </LightningComponentBundle> |
Conclusion: Now
let's deploy our code and drag-drop our grantParent component to the
home page to test our so far discussed solution. Our home page looks
like below
Let's examine the behavior based on certain combination.
-
bubbles:false and composed:false - This is same as firing event
without these event properties since they are defaulted to false by
component framework. Just look at grandChild.js file and firing of
event.
const customEvent = new CustomEvent('notify');
which is same as
| const customEvent = new CustomEvent('notify', { |
| bubbles: false, |
| composed: false |
| }); |
Now when you click on Hit Me On Grand Child button on
Grand Child component, see the output
Event is stopped to bubble up higher because of SHADOW DOM boundary. It
can't go past the immediate container and hence Child is the ultimate
destination where the event has stopped its propagation.
-
bubbles:true and composed:false -
| const customEvent = new CustomEvent('notify', { |
| bubbles: true, |
| composed: false |
| }); |
Here again since the bubbles property is set to true but
composed property, which is responsible for an event to cross the
SHADOW Boundary, is set to false so we don't see much change in the
behavior and output is same as above.
-
bubbles:true and composed:true -
| const customEvent = new CustomEvent('notify', { |
| bubbles: true, |
| composed: true |
| }); |
Here since bubbles property is set to true which allows
the event to bubble up the hierarchy and setting
composed property to true makes the event
accessible outside the SHADOW DOM but with the concept of event
retargeting as discussed under Configure Event Propagation section.
Look at the output now.
The event notify has crossed the SHADOW Boundary and reached to its
root i.e. Grand Parent in the containment hierarchy.
Stopping the Event Propagation: There might be situation where we don't want the event to be propagated
after certain level, how to stop this propagation. Consider we don't want
our event to be available after Parent call. Well, simply replace
the handleNotify function in parent.js file with the
following
parent.js
| handleNotify(event) { |
| event.preventDefault(); |
| this.text = 'Notify event listened in parent.'; |
| console.log(this.text); |
| event.stopPropagation(); |
| } |
Now as you hit the button on Grand Child and compare the result
The event has stopped its propagation beyond the Parent and thus the
Grand Parent is no longer able to listen to this notify event.
If you like this blog content and find inciteful, please comment and
let me know.
Great blog! Very informative.
ReplyDeleteIf I may, I would like to ask a question.
I understand that we use event.detail when we are listening to this notify event from a child or up the hierarchy. When do we use event.target? Could you explain this from the above example?
Sure, Thanks for asking this question. I will explain the event.target and event.detail in my upcoming blog.
DeleteThank you so much.
ReplyDeletewe use Event.Target to get the access or reference of the component who has fired that event. As it is already mentioned in blog because of event retargeting it will give the reference of only 'c-grand-child' not the 'lightning-button' to preserve the encapsulation. so in parent if you console the event.target.value it will print 'c-grand-child'.
ReplyDeleteAmazing blog and the gem in crown is the example which explained it so easily when its theory seemed mind boggling. Can you also explain Lightning Messaging Service ?
ReplyDeleteThanks for the feedback. I will definitely cover Lightning Messaging Service in my upcoming blog.
DeleteYou can now check out my new blog as requested by you on Lightning Message Service
ReplyDeletehttps://inevitableyogendra.blogspot.com/2021/06/communicate-across-dom-with-lightning-message-service.html
Hope this will be equally helpful in your learning curve.
while executing your code, getting the error, pls advise.
ReplyDeleteAction.prototype.finishAction Error [Error in $A.getCallback() [LightningElement is not defined]
Callback failed: serviceComponent://flexipage.editor.aura.component.FlexipageComponentController/ACTION$loadComponentDefinitions]
new Aura.externalLibraries()@https://playful-hawk-5k9krt-dev-ed.lightning.force.com/auraFW/javascript/7FPkrq_-upw5gdD4giTZpg/aura_prod.js:354:407
Amazing blog and very profitable. thanks.
ReplyDeletei have a few doubts :
ReplyDelete1. If the properties are set to bubble : false n composed : false but still its crossing the shadow DOM and here child can react to the event triggered in grandchild. But as u have said if composed : false then how its crossing the shadow DOM.
2. whats the use of bubble : true and composed : false as its showing the same result as bubble : false n composed : false. Pease cite an real time example .
3. So when a child component is wrapped by the parent component . Then only the shadow DOM gets created or is there any way by which shadom DOM can be created into the component itself.
4. As you are calling the grandchild component in child and the granchild component in parent as well as in grand parent. So i this manner the child , parent and grandparent would be siblings because if grandchild is called in child , child called into parent and parent called into child the it would have formed a hierarchy.
Please clear my doubts thanks in advance.
i have a few doubts :
ReplyDelete1. If the properties are set to bubble : false n composed : false but still its crossing the shadow DOM and here child can react to the event triggered in grandchild. But as u have said if composed : false then how its crossing the shadow DOM. Its contadicting the statement.
2. whats the use of bubble : true and composed : false as its showing the same result as bubble : false n composed : false. Please cite an real time example .
3. So when a child component is wrapped by the parent component . Then only the shadow DOM gets created or is there any way by which shadom DOM can be created into a component itself.
4. As you are calling the grandchild component in child and the granchild component in parent as well as in grand parent. So i this manner the child , parent and grandparent would be siblings because if grandchild is called in child , child called into parent and parent called into grand parent then it would have formed a hierarchy.
Please clear my doubts thanks in advance.
This comment has been removed by the author.
Deletepegulsfig-hiYonkers Mike Mejia https://www.synergyphysiopilates.ca/profile/Cyberplanet-62-Premium-Full-Crack-Fixed/profile
ReplyDeleteemerenpref