Lightning Web Components Slots
Sunday With Lightning Episode 7 #Lightning #Web #Component #Slot #Events Topics covered:- 1. Handling multiple child Components in Parent LWC 2. Salesforce Lightning Web Components - LWC Interview Questions; Reusable Custom LWC Lookup Component; How to invoke/call child lightning web component function from parent lightning web component; How To Add Hyper Link For Name Column In lightning-datatable in LWC; Navigate/Redirect To Record Page Based On Record Id In LWC (Lightning Web Component).
I've blogged about a few of the behaviours of Lightning Web Components, but the proof is really in building useful bits. What happens when you actually try to make a re-usable component?
For our example, we'll rebuild 'ui:message'. A now (seemingly) defunct base component that would render a message in a box that is coloured based on the 'severity' of the message being shown. In the original it could be set to 'closable', although we're going to ignore that and focus on just the rendering of it.
In a Lightning component we would use it like this:
Original usage - Lightning Component
Ideally, the version we will create, would be used like this:
Desired usage
Looks pretty straightforward, and actually - it is. Just as long as we know about a few simple concepts.
Before we go into them, let's see what a working example could look like:
Javascript component
HTML Template
OK then, let's pick a few of these bits apart, and hopefully we'll explain a few little behaviours along the way.
First up, let's take a look at the '@api' declarations.
@api
The @api property lines are pretty simple to understand - they define that 'title' and 'severity' are publicly available properties of the component. In the context of Lightning Web Components, public and private mean 'available outside of the component, and invisible to the outside of the component'. It's tempting to think that this is to do with the scope of the Javascript, but it's not.
That is, every property of the Javascript component is available to be referenced in the HTML template - whether it is 'public' or 'private'. One way of thinking about it is that the HTML template forms part of the component, and so it can see private properties.
Another (probably more accurate) way of thinking about it is that the template is processed by the Javascript component (that code it's immediately obvious, but it's almost certainly in LightningComponent - which this class extends), and the Javascript can see its own properties, so the private ones are available.
However, other components (like ones that include this in their templates) can only see public properties. @api is how you make them public. Doing so means that they are available as attributes on the tag you use to include the component (hence <c-message... is possible)
Not only that, but every @api decorated property is also 'reactive'. That is, whenever its value changes the component is re-rendered. The documentation is pretty clear on that point - and is presented as a fundamental property of a public property:
Public PropertiesTo expose a public property, decorate it with @api. Public properties define the API for a component. An owner component that uses the component in its markup can access the component’s public properties. Public properties are reactive. If the value of a reactive property changes, the component’s template rerenders any content that references the property.
Why would a public property be reactive?
Put simply, if we change the value of one of those properties in a parent component, we want the component to re-render - and it's pretty much guaranteed that we ALWAYS want the component to re-render.
For example, we may do the following:
When the value of 'title' or 'severity' changes, we would always want the message box to re-render to show our new values. And so the framework takes care of that and makes EVERY public property of the component reactive
So that takes care of the attributes we need to pass in, what about the content?
Slots
Lightning Components had facets. And they weren't intuitive. I mean they weren't complex, but they weren't in keeping with HTML - they always felt unnatural - especially in the simplest of cases.
Lightning Web Components fixes that, with slots. And in the simple case they are trivial. The documentation isn't long, and doesn't need to be.
All we need to do, in this simple case, is add <slot></slot> into our component, and the body of any tag that instantiates the component will be rendered in that slot.
Now something that's missing from the documentation, which is a fairly obvious behaviour once you see it in action, is that slots are effectively reactive.
That is, if you change the content of the tag, that content is immediately reflected in the component's rendered output.
So, in our example:
Whenever the value of 'errorMessages' changes, the slot inside the 'message' component is re-rendered to include the new content.
I admit, I had assumed that this would be the case, but I didn't immediately realise that it was an assumption. So I thought it was worth calling out
Getters
The final part of the example that I want to explain is the use of the 'getter':
Lightning Web Components Slots Free
What we're doing here is building a list of CSS classes for a node in the component that includes one of the passed in attributes plus a standard class that must be applied
The use of the getter illustrates an important difference between the behaviour of the templates in Lightning Components (LC) and Lightning Web Components (LWC), as well a reminder of the behaviour of properties.
That is, in LC we could have done the following in our template:
In LC, our replacements could include expressions, so we could build up strings in the template. In LWC, we can't do this, we can only reference properties or getters.
Not only that, but we can't build up the strings in the attribute assignment.
I.E. We can't do this:
In LWC we don't assign properties to attributes in this way, the framework takes care of the wrapping in double quotes, escaping the strings, and other such things, so we can only assign the property, and that's it.
I.E. This is what is allowed:
So, if we want to assign more than just the value of 'severity' to the class attribute, we need to build that string up outside of the template.
Your first reaction might be - OK, we can create a trackable property to store it, right?
But this doesn't work. You'll end up with the classes property defined as 'undefined uiMessage', and it won't change. Why is that?
Well, it's tempting to think that 'track' and 'api' mean that Javascript will re-run when things change, but that's not what they do - nor what the documentation says they'll do
Rather, if a property is reactive it means that the component will be re-rendered when the property changes. That says nothing about running Javascript.
So when we look at the above, what happens is the property 'classes' is set when the Javascript object is constructed. At this point the property 'severity' is undefined. When the 'severity' is updated via the attribute, the component is re-rendered and the 'classes' property is re-injected into the template, but the Javascript that sets the classes property is not re-run - that is only executed when the object is instantiated.
So, instead of setting the 'classes' property directly, we set up a getter for it:
Javascript component
Now, when the 'severity' property changes, the 'classes' property is re-injected. In order to get the value for 'classes', the getter is executed - this is the only way the property can be retrieved. In doing so, the string concatenation is re-evaluated and the new value is retrieved.
Summary
None of the concepts here are particularly difficult, or really that earth shattering, but building even the simplest of re-usable components starts to shed some light on what the parts do any why.
The framework has been very thoughtfully put together, and some of the techniques will be different to what people are used to, having cut their Javascript teeth with Lightning Components, but the changes are for very good reasons. An example like this really shows how those changes make for simple components.
Lightning web components dispatch standard DOM events. Components can also create and dispatch custom events.
Use events to communicate up the component containment hierarchy. For example, a child component, example-todo-item
, dispatches an event to tell its parent, example-todo-app
, that a user selected it.
Events in Lightning web components are built on DOM Events, a collection of APIs and objects available in every browser.
Lightning web components implement the EventTarget
interface, which allows them to dispatch events, listen for events, and handle events.
To create events, we strongly recommend using the CustomEvent
interface instead of the Event
interface. In Lightning web components, CustomEvent
provides a more consistent experience across browsers, including Internet Explorer.
Dispatching events or adding listeners to the capture phase isn't supported. Simply think of the event’s path as starting with your component, and then moving to its parent, and then grandparent, and so on.
Tip
To communicate up the component hierarchy, use events. To communicate down the component hierarchy, pass properties to a child via HTML attributes, or call its public methods.
# Handle Events
Lightning Web Components Slots Games
There are two ways to listen for an event: declaratively from the component’s HTML template, or programmatically using an imperative JavaScript API. It’s better to listen from the HTML template since it reduces the amount of code you need to write.
To handle an event, define a method in the component’s JavaScript class.
# Attach Event Listeners Declaratively
This example uses two components in the example
namespace, <example-owner>
and <example-child>
.
The child component has an HTML button, which emits a standard click
event.
To listen for an event, a component uses an HTML attribute with the syntax oneventtype
. In the template of the owner component, to listen for the click
event emitted from <example-child>
, declare the listener onclick
.
In the JavaScript class of the owner component, define the handleClick
method, which executes when the click
event fires.
# Attach Event Listeners in JavaScript
Alternately, you can define both the listener and the handler in the owner component's JavaScript file.
Get a reference to <example-child>
using this.template.querySelector
. To handle the event, define handleClick
in the JavaScript file of the owner.
There are two syntaxes for adding an event listener. One adds an event listener to an element within a component’s shadow boundary. One adds an event listener to an element that the template doesn’t own, for example, an element passed into a slot.
To add an event listener to an element within the shadow boundary, use template
.
In the previous example, the parent.js
code uses this.template
syntax to select example-child
because example-child
is within its shadow boundary. An event listener added via this.template.addEventListener
has access to bubbling events inside the shadow tree.
To add an event listener to an element that a template doesn’t own, call addEventListener
directly.
An event listener added via this.addEventListener
binds to the host element and has access to events on the host element and to any bubbling events from slotted content. It doesn't have access to events inside the shadow tree. See Pass Markup into Slots.
# Remove Event Listeners
As part of the component lifecycle, the browser manages and cleans up listeners, so you don’t have to worry about it.
However, if you add a listener to the global window
object, you’re responsible for removing the listener yourself within the appropriate lifecycle hook. In this case, use the connectedCallback
and disconnectedCallback
methods to add and remove the event listeners.
# Create and Dispatch Events
Create and dispatch events in a component’s JavaScript class. To create an event, use the CustomEvent()
constructor. To dispatch an event, call the EventTarget.dispatchEvent()
method.
The CustomEvent()
constructor has one required parameter, which is a string indicating the event type. As a component author, you name the event type when you create the event. The event type is the name of the event. You can use any string as your event type. However, we recommend that you conform with the DOM event standard.
- No uppercase letters
- No spaces
- Use underscores to separate words
Don’t prefix your event name with the string on
, because inline event handler names must start with the string on
. If your event is called onmessage
, the markup would be <example-my-component ononmessage={handleMessage}>
. Notice the doubled word onon
, which is confusing.
Tip
To learn more about events, read the MDN Introduction to events and see this list of standard Event types.
Let’s jump into some code.
The paginator
component contains Previous and Next buttons. When a user clicks a button, the handlePrevious
or handleNext
function executes. These functions create and dispatch the previous
and next
events. You can drop the paginator
component into any component that needs Previous and Next buttons. That component listens for the events and handles them.
These events are simple something happened events. They don’t pass a data payload up the DOM tree, they simply announce that a user clicked a button.
Let’s drop paginator
into a component called eventSimple
, which listens for and handles the previous
and next
events.
In the playground, click the eventSimple.html
tab. To listen for events, use an HTML attribute with the syntax oneventtype
. Since our event types are previous
and next
, the listeners are onprevious
and onnext
.
Click the eventSimple.js
tab. When eventSimple
receives the previous
and next
events, the handlePrevious
and handleNext
functions increase and decrease the page number.
# Pass Data in Events
To communicate data to elements in the same shadow tree, the consumer of the event can access the property off event.target
, which is a reference to the object that dispatched the event. For example, a component could access event.target.myProperty
. In this case, don't add the property to event.detail
.
To communicate data to elements that aren’t in the same shadow tree, use event.detail
. In these cases, event.target.*
doesn’t work because the real target isn’t visible to the listener. (When an event bubbles up the DOM, if it crosses the shadow boundary, the value of Event.target
changes to match the scope of the listener. The event is retargeted so the listener can’t see into the shadow tree of the component that dispatched the event.)
Receiving components access the data in the detail
property in the event listener’s handler function.
Important
The CustomEvent
interface imposes no type requirements or structure on the detail
property. However it’s important to send only primitive data. JavaScript passes all data types by reference except for primitives. If a component includes an object in its detail
property, any listener can mutate that object without the component’s knowledge. This is a bad thing! It’s a best practice either to send only primitives, or to copy data to a new object before adding it to the detail
property. Copying the data to a new object ensures that you’re sending only the data you want, and that the receiver can’t mutate your data.
Example
Check out the eventWithDetail
component in the lwc-recipes-oss repo.
# Get an Event Target
To get a reference to the object that dispatched the event, use the Event.target
property, which is part of the DOM API for events.
Example
Several recipes in the lwc-recipes-oss repo use Event.target
. To find examples, search the repo for event.target
.
When an event bubbles up the DOM, if it crosses 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.
Let’s look at a simple example.
A click
listener on <my-button>
always receives my-button
as the target, even if the click happened on the button
element.
Imagine an event is dispatched from a div
element in the example-todo-item
component. Within the component’s shadow DOM, Event.target
is div
. But to a listener on the p
element in the containing example-todo-app
component, the Event.target
is example-todo-item
, because the p
element can’t see into the example-todo-item
shadow DOM.
It’s interesting to note that to a listener on example-todo-item
, the Event.target
is example-todo-item
, not div
, because example-todo-item
is outside the shadow boundary.
# Configure Event Propagation
After an event is fired, it can propagate up through the DOM. To understand where events can be handled, understand how they propagate. Lightning web component events propagate according to the same rules as DOM events. When you create an event, define event propagation behavior using two properties on the event, bubbles
and composed
.
bubbles
A Boolean value indicating whether the event bubbles up through the DOM or not. Defaults to false
.
composed
A Boolean value indicating whether the event can pass through the shadow boundary. Defaults to false
.
Salesforce Lightning Web Component Slot
#bubbles: false
and composed: false
The default configuration. The event doesn’t bubble up through the DOM and doesn’t cross the shadow boundary. The only way to listen to this event is to add an event listener directly on the component that dispatches the event.
This configuration is recommended because it’s the least disruptive.
In this example, example-my-component
creates and dispatches a simple event called notify
.
example-my-component
is nested in example-app
. The example-app
template adds an event listener on example-my-component
to listen for the notify
event.
When example-app
hears the event, its handleNotify()
function executes.
Example
The eventWithData
component in the lwc-recipes-oss repo consumes a contactListItem
component, which creates an event with bubbles: false
and composed: false
.
#bubbles: true
and composed: false
The event bubbles up through the DOM, but doesn’t cross the shadow boundary.
There are two ways to bubble up an event:
- Bubble an event up inside the component’s template. This technique creates an internal event.
- Bubble an event up inside the containing component’s template. Use this technique to send an event to a component's grandparent when the component is passed into a slot. Note that this technique works only with the LWC synthetic shadow DOM. With native shadow DOM, the event doesn't pass out of the slot unless
composed
is alsotrue
.
To bubble an event inside the component’s template, dispatch the event on an element in the template. The event bubbles up to the element’s ancestors inside the template only. When the event reaches the shadow boundary, it stops.
The event must be handled in myComponent.js
. Handlers in the containing component don’t execute because the event doesn’t cross the shadow boundary.
We don’t recommend bubbling events outside of your own template, but it’s possible. To bubble an event to the template that contains your component, dispatch the event on the host element. The event is visible only in the template that contains your component.
Example
The eventBubbling
component in the lwc-recipes-oss repo consumes a contactListItemBubbling
component, which creates an event with bubbles: true
and composed: false
. This recipe uses slots and sends an event to a grandparent. Note that this technique works only with the LWC synthetic shadow DOM. With native shadow DOM, the event doesn't pass out of the slot unless composed
is also true
.
#bubbles: true
and composed: true
The event bubbles up through the DOM, crosses the shadow boundary, and continues bubbling up through the DOM to the document root.
This configuration is an anti-pattern because it creates an event that crosses every boundary. Every element gets the event, even the regular DOM elements that aren’t part of any shadow. The event can bubble all the way up to the body element.
When firing events this way, you can pollute the event space, leak information and create confusing semantics. Events are considered part of your component’s API, so make sure that anyone on the event path is able to understand and handle the event’s payload if it has one.
Important
If an event uses this configuration, the event type becomes part of the component’s public API. It also forces the consuming component and all of its ancestors to include the event as part of their APIs. Because this configuration bubbles your event all the way to the document root, it can cause name collisions. Name collisions can cause the wrong event listeners to fire. You may want to prefix your event type with a namespace, like mydomain__myevent
. The HTML event listener would have the awkward name onmydomain__myevent
.
#bubbles: false
and composed: true
Composed events can break shadow boundaries and bounce from host to host along their path. They don’t continue to bubble beyond that, unless they also set bubbles: true
.
This configuration is an anti-pattern, but it’s helpful for understanding how events bubble in a shadow DOM context.
Lightning Web Components Slots No Deposit
In this case, c-child
, c-parent
, and c-container
can react to the event. It’s interesting to note that div.childWrapper
can’t handle the event, because the event doesn't bubble in the shadow itself.
Lightning Web Components Slots Free Play
← Fields, Properties, and AttributesComponent Lifecycle →