Events
Events are the standard way that elements communicate changes. These changes typically occur due to user interaction. For example, a button dispatches a click event when a user clicks on it; an input dispatches a change event when the user enters a value in it.
In addition to these standard events that are automatically dispatched, Lit elements can dispatch custom events. For example, a menu element might dispatch an event to indicate the selected item changed; a popup element might dispatch an event when the popup opens or closes.
Any Javascript code, including Lit elements themselves, can listen for and take action based on events. For example, a toolbar element might filter a list when a menu item is selected; a login element might process a login when it handles a click on the login button.
Listening to events
Permalink to “Listening to events”In addition to the standard addEventListener
API, Lit introduces a declarative way to add event listeners.
Adding event listeners in the element template
Permalink to “Adding event listeners in the element template”You can use @
expressions in your template to add event listeners to elements in your component's template. Declarative event listeners are added when the template is rendered.
Customizing event listener options
Permalink to “Customizing event listener options”If you need to customize the event options used for a declarative event listener (like passive
or capture
), you can specify these on the listener using the @eventOptions
decorator. The object passed to @eventOptions
is passed as the options
parameter to addEventListener
.
Using decorators. Decorators are a proposed JavaScript feature, so you’ll need to use a compiler like Babel or TypeScript to use decorators. See Enabling decorators for details.
If you're not using decorators, you can customize event listener options by passing an object to the event listener expression. The object must have a handleEvent()
method and can include any the options that would normally appear in the options
argument to addEventListener()
.
Adding event listeners to the component or its shadow root
Permalink to “Adding event listeners to the component or its shadow root”To be notified of an event dispatched from the component's slotted children as well as children rendered into shadow DOM via the component template, you can add a listener to the component itself using the standard addEventListener
DOM method. See EventTarget.addEventListener() on MDN for full details.
The component constructor is a good place to add event listeners on the component.
Adding event listeners to the component itself is a form of event delegation and can be done to reduce code or improve performance. See event delegation for details. Typically when this is done, the event's target
property is used to take action based on which element fired the event.
However, events fired from the component's shadow DOM are retargeted when heard by an event listener on the component. This means the event target is the component itself. See Working with events in shadow DOM for more information.
Retargeting can interfere with event delegation, and to avoid it, event listeners can be added to the component's shadow root itself. Since the shadowRoot
is not available in the constructor
, event listeners can be added in the createRenderRoot
method as follows. Please note that it's important to make sure to return the shadow root from the createRenderRoot
method.
Adding event listeners to other elements
Permalink to “Adding event listeners to other elements”If your component adds an event listener to anything except itself or its templated DOM – for example, to Window
, Document
, or some element in the main DOM – you should add the listener in connectedCallback
and remove it in disconnectedCallback
.
Removing the event listener in
disconnectedCallback
ensures that any memory allocated by your component will be cleaned up when your component is destroyed or disconnected from the page.Adding the event listener in
connectedCallback
(instead of, for example, the constructor orfirstUpdated
) ensures that your component will re-create its event listener if it is disconnected and subsequently reconnected to DOM.
See the MDN documentation on using custom elements lifecycle callbacks for more information on connectedCallback
and disconnectedCallback
.
Optimizing for performance
Permalink to “Optimizing for performance”Adding event listeners is extremely fast and typically not a performance concern. However, for components that are used in high frequency and need a lot of event listeners, you can optimize first render performance by reducing the number of listeners used via event delegation and adding listeners asynchronously after rendering.
Event delegation
Permalink to “Event delegation”Using event delegation can reduce the number of event listeners used and therefore improve performance. It is also sometimes convenient to centralize event handling to reduce code. Event delegation can only be use to handle events that bubble
. See Dispatching events for details on bubbling.
Bubbling events can be heard on any ancestor element in the DOM. You can take advantage of this by adding a single event listener on an ancestor component to be notified of a bubbling event dispatched by any of its descendants in the DOM. Use the event's target
property to take specific action based on the element that dispatched the event.
Asynchronously adding event listeners
Permalink to “Asynchronously adding event listeners”To add an event listener after rendering, use the firstUpdated
method. This is a Lit lifecycle callback which runs after the component first updates and renders its templated DOM.
The firstUpdated
callback fires after the first time your component has been updated and called its render
method, but before the browser has had a chance to paint.
See firstUpdated in the Lifecycle documentation for more information.
To ensure the listener is added after the user can see the component, you can await a Promise that resolves after the browser paints.
Understanding this
in event listeners
Permalink to “Understanding this in event listeners” Event listeners added using the declarative @
syntax in the template are automatically bound to the component.
Therefore, you can use this
to refer to your component instance inside any declarative event handler:
When adding listeners imperatively with addEventListener
, you'll want to use an arrow function so that this
refers to the component:
See the documentation for this
on MDN for more information.
Listening to events fired from repeated templates
Permalink to “Listening to events fired from repeated templates”When listening to events on repeated items, it's often convenient to use event delegation if the event bubbles. When an event does not bubble, a listener can be added on the repeated elements. Here's an example of both methods:
Removing event listeners
Permalink to “Removing event listeners”Passing null
, undefined
or nothing
to an @
expression will cause any existing listener to be removed.
Dispatching events
Permalink to “Dispatching events”All DOM nodes can dispatch events using the dispatchEvent
method. First, create an event instance, specifying the event type and options. Then pass it to dispatchEvent
as follows:
The bubbles
option allows the event to flow up the DOM tree to ancestors of the dispatching element. It's important to set this flag if you want the event to be able to participate in event delegation.
The composed
option is useful to set to allow the event to be dispatched above the shadow DOM tree in which the element exists.
See Working with events in shadow DOM for more information.
See EventTarget.dispatchEvent() on MDN for a full description of dispatching events.
When to dispatch an event
Permalink to “When to dispatch an event”Events should be dispatched in response to user interaction or asynchronous changes in the component's state. They should generally not be dispatched in response to state changes made by the owner of the component via its property or attribute APIs. This is generally how native web platform elements work.
For example, when a user types a value into an input
element a change
event is dispatched, but if code sets the input
's value
property, a change
event is not dispatched.
Similarly, a menu component should dispatch an event when the user selects a menu item, but it should not dispatch an event if, for example, the menu's selectedItem
property is set.
This typically means that a component should dispatch an event in response to another event to which it is listening.
Dispatching events after an element updates
Permalink to “Dispatching events after an element updates”Often, an event should be fired only after an element updates and renders. This might be necessary if an event is intended to communicate a change in rendered state based on user interaction. In this case, the component's updateComplete
Promise can be awaited after changing state, but before dispatching the event.
Using standard or custom events
Permalink to “Using standard or custom events”Events can be dispatched either by constructing an Event
or a CustomEvent
. Either is a reasonable approach. When using a CustomEvent
, any event data is passed in the event's detail
property. When using an Event
, an event subclass can be made and custom API attached to it.
See Event on MDN for details about constructing events.
Firing a custom event:
Permalink to “Firing a custom event:”
See the MDN documentation on custom events for more information.
Firing a standard event:
Permalink to “Firing a standard event:”
Working with events in shadow DOM
Permalink to “Working with events in shadow DOM”When using shadow DOM there are a few modifications to the standard event system that are important to understand. Shadow DOM exists primarily to provide a scoping mechanism in the DOM that encapsulates details about these "shadow" elements. As such, events in shadow DOM encapsulate certain details from outside DOM elements.
Understanding composed event dispatching
Permalink to “Understanding composed event dispatching”By default, an event dispatched inside a shadow root will not be visible outside that shadow root. To make an event pass through shadow DOM boundaries, you must set the composed
property to true
. It's common to pair composed
with bubbles
so that all nodes in the DOM tree can see the event:
If an event is composed
and does bubble
, it can be received by all ancestors of the element that dispatches the event—including ancestors in outer shadow roots. If an event is composed
but does not bubble
, it can only be received on the element that dispatches the event and on the host element containing the shadow root.
Note that most standard user interface events, including all mouse, touch, and keyboard events, are both bubbling and composed. See the MDN documentation on composed events for more information.
Understanding event retargeting
Permalink to “Understanding event retargeting”Composed events dispatched from within a shadow root are retargeted, meaning that to any listener on an element hosting a shadow root or any of its ancestors, they appear to come from the hosting element. Since Lit components render into shadow roots, all composed events dispatched from inside a Lit component appear to be dispatched by the Lit component itself. The event's target
property is the Lit component.
In advanced cases where it is required to determine the origin of an event, use the event.composedPath()
API. This method returns an array of all the nodes traversed by the event dispatch, including those within shadow roots. Because this breaks encapsulation, care should be taken to avoid relying on implementation details that may be exposed. Common use cases include determining if the element clicked was an anchor tag, for purposes of client-side routing.
See the MDN documentation on composedPath for more information.
Communicating between the event dispatcher and listener
Permalink to “Communicating between the event dispatcher and listener”Events exist primarily to communicate changes from the event dispatcher to the event listener, but events can also be used to communicate information from the listener back to the dispatcher.
One way you can do this is to expose API on events which listeners can use to customize component behavior. For example, a listener can set a property on a custom event's detail property which the dispatching component then uses to customize behavior.
Another way to communicate between the dispatcher and listener is via the preventDefault()
method. It can be called to indicate the event's standard action should not occur. When the listener calls preventDefault()
, the event's defaultPrevented
property becomes true. This flag can then be used by the listener to customize behavior.
Both of these techniques are used in the following example: