Events
This documentation is a work in progress. It describes prerelease software, and is subject to change.
TODO: Tidy up this page
- Overview
- Add an event listener to a LitElement host
- Add an event listener to the child element of a LitElement host
- Add an event listener to anything else
- Bubbling, custom events, composed path, etc
Overview
- Default event context is
this
- no need forthis.handlerMethod.bind(this)
- You can use
this
referring to the host inside an event handler - Add event listeners in a method that is guaranteed to fire before the event occurs
- For optimal loading performance, add your event listener as late as possible
Add an event listener to a LitElement host
You can use the firstUpdated
callback to add an event listener after the element has first been rendered:
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
firstUpdated() {
this.addEventListener('click', this.clickHandler);
}
clickHandler(e) {
console.log(e.target);
}
render() {
return html`
<p>Click me</p>
`;
}
}
customElements.define('my-element', MyElement);
If your event might occur before the element has finished rendering, you can add the listener from the constructor.
Add an event listener to the child element of a LitElement host
Option 1: Use lit-html template syntax
You can add an event listener to an element’s DOM child using lit-html data binding syntax:
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
render() {
return html`
<button @click="${this.doThing}">click me</button>
`;
}
doThing(e) {
console.log(e);
}
}
customElements.define('my-element', MyElement);
See the documentation on Templates for more information on lit-html template syntax.
Option 2: Add the listener imperatively
To add an event listener to a child element imperatively, you must do so in or after firstUpdated
to ensure that the child element exists. For example:
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
firstUpdated() {
let button = this.shadowRoot.getElementById('mybutton');
button.addEventListener('click', this.doThing);
}
render() {
return html`
<button id="mybutton">click me</button>
`;
}
doThing(e) {
console.log(e);
}
}
customElements.define('my-element', MyElement);
Add an event listener to anything else
When you’re adding an event listener to the host element or its children, you don’t need to worry about memory leaks. The memory allocated to the event listener will be destroyed when the host element is destroyed.
However, when adding an event listener to something other than the host element or its children, you should add the listener in connectedCallback
and remove it in disconnectedCallback
:
connectedCallback() {
super.connectedCallback();
window.addEventListener('hashchange', this.hashChangeListener);
}
disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener('hashchange', this.hashChangeListener);
}
Why
Because garbage collection.
Bad
- MyElement creates an event listener on Window
- Event listener fires, memory is allocated in its scope
- MyElement is destroyed
However, the memory allocated to the event listener is still in use. To avoid this:
Good
- MyElement adds event listener to Window
- Event listener fires, memory is allocated in its scope
- MyElement’s disconnectedCallback fires, event listener is removed from Window
Memory is freed up and all is well.
Also Good
If the element can move around the DOM during its lifecycle, you may need to add the event listener in connectedCallback
:
- MyElement is appended to ParentElement, MyElement’s connectedCallback fires, event listener is added to ParentElement
- Event listener fires, memory is allocated in its scope
- MyElement is moved to a new position in DOM, disconnectedCallback fires, event listener is removed from ParentElement, memory is freed
- MyElement is appended to OtherElement, connectedCallback fires again, etc
Bubbling, custom events, composed path, etc
Event bubbling
//?????
constructor() {
this.super();
this.addEventListener('click', (e) => { console.log(e.target)});
}
render() {
return html`<button id="mybutton" @click="${this.handleClick}"></button>`;
}
handleClick(e) {
console.log(e.target.id);
}
Event retargeting
render() {
return html`<button id="mybutton" @click="${this.handleClick}"></button>`;
}
handleClick(e) {
console.log(e.target.id);
}
Composed path
render() {
return html`<button id="mybutton" @click="${this.handleClick}"></button>`;
}
handleClick(e) {
let origin = e.composedPath()[0];
console.log(origin.id);
}
By default, custom events stop at shadow DOM boundaries. To make a custom event pass through shadow DOM boundaries, set the composed flag to true when you create the event:
Composed path
constructor() {
this.super();
this.addEventListener('my-event', (e) => { console.log(e.composedPath())});
}
render() {
return html`<button id="mybutton" @click="${this.doThing}"></button>`;
}
doThing() {
let myEvent = new CustomEvent('my-event', { bubbles: true, composed: true });
this.dispatchEvent(myEvent);
}
Something. Something something.
code.
Something.
Remember to something. Something something.