Lifecycle
This documentation is a work in progress. It describes prerelease software, and is subject to change.
Overview
LitElement-based components update asynchronously in response to observed property changes. Property changes are batched—if more properties change after an update is requested, but before the update starts, all of the changes are captured in the same update.
At a high level, the update lifecycle is:
- A property is set.
- Check whether an update is needed. If an update is needed, request one.
- Perform the update:
- Process properties and attributes.
- Render the element.
- Resolve a Promise, indicating that the update is complete.
LitElement and the browser event loop
The browser executes JavaScript code by processing a queue of tasks in the event loop. In each iteration of the event loop, the browser takes a task from the queue and runs it to completion.
When the task completes, before taking the next task from the queue, the browser allocates time to perform work from other sources—including DOM updates, user interactions, and the microtask queue.
By default, LitElement updates are requested asynchronously, and queued as microtasks. This means that Step 3 above (Perform the update) is executed at the end of the next iteration of the event loop.
You can change this behavior so that Step 3 awaits a Promise before performing the update. See performUpdate
for more information.
For a more detailed explanation of the browser event loop, see Jake Archibald’s article.
Promises and asynchronous functions
LitElement uses Promise objects to schedule and respond to element updates.
Using async
and await
makes it easy to work with Promises. For example, you can await the updateComplete
Promise:
// `async` makes the function return a Promise & lets you use `await`
async myFunc(data) {
// Set a property, triggering an update
this.myProp = data;
// Wait for the updateComplete promise to resolve
await this.updateComplete;
// ...do stuff...
return 'done';
}
Because async
functions return a Promise, you can await them, too:
let result = await myFunc('stuff');
// `result` is resolved! You can do something with it
See the Web Fundamentals primer on Promises for a more in-depth tutorial.
Methods and properties
In call order, the methods and properties in the update lifecycle are:
- someProperty.hasChanged
- requestUpdate
- performUpdate
- shouldUpdate
- update
- render
- firstUpdated
- updated
- updateComplete
someProperty.hasChanged
All declared properties have a function, hasChanged
, which is called whenever the property is set; if hasChanged
returns true, an update is scheduled.
See the Properties documentation for information on configuring hasChanged
to customize what constitutes a property change.
requestUpdate
// Manually start an update
this.requestUpdate();
// Call from within a custom property setter
this.requestUpdate(propertyName, oldValue);
Params |
propertyName oldValue |
Name of property to be updated. Previous property value. |
Returns | Promise |
Returns the updateComplete Promise, which resolves on completion of the update. |
Updates? | No | Property changes inside this method will not trigger an element update. |
If hasChanged
returned true
, requestUpdate
fires, and the update proceeds.
To manually start an element update, call requestUpdate
with no parameters.
To implement a custom property setter that supports property options, pass the property name and its previous value as parameters.
Example: Manually start an element update
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
constructor() {
super();
// Request an update in response to an event
this.addEventListener('load-complete', async (e) => {
console.log(e.detail.message);
console.log(await this.requestUpdate());
});
}
render() {
return html`
<button @click="${this.fire}">Fire a "load-complete" event</button>
`;
}
fire() {
let newMessage = new CustomEvent('load-complete', {
detail: { message: 'hello. a load-complete happened.' }
});
this.dispatchEvent(newMessage);
}
}
customElements.define('my-element', MyElement);
Example: Call requestUpdate
from a custom property setter
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
static get properties() { return {
prop1: { type: Number, noAccessors: true },
prop2: { type: Number },
prop3: { type: Number, noAccessors: true },
};}
set prop1(newVal) { this._prop1 = Math.floor(newVal); }
set prop2(newVal) { this._prop2 = Math.floor(newVal); }
set prop3(newVal) {
let oldVal = this._prop3;
this._prop3 = Math.floor(newVal);
this.requestUpdate('prop3', oldVal);
}
get prop1() { return this._prop1; }
get prop2() { return this._prop2; }
get prop3() { return this._prop3; }
constructor() {
super();
this._prop1 = 0;
this._prop2 = 0;
this._prop3 = 0;
}
render() {
return html`
<p>prop1: ${this.prop1}</p>
<p>prop2: ${this.prop2}</p>
<p>prop3: ${this.prop3}</p>
<button @click="${this.getNewVal}">change properties</button>
`;
}
updated(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
console.log(`${propName} changed. oldValue: ${oldValue}`);
});
}
getNewVal() {
let newVal = Math.random()*10;
this.prop1 = newVal;
this.prop2 = newVal;
this.prop3 = newVal;
}
}
customElements.define('my-element', MyElement);
performUpdate
/**
* Implement to override default behavior.
*/
performUpdate() { ... }
Returns | void or Promise |
Performs an update. |
Updates? | No | Property changes inside this method will not trigger an element update. |
By default, performUpdate
is scheduled as a microtask after the end of the next execution of the browser event loop. To schedule performUpdate
, implement it as an asynchronous method that awaits some state before calling super.performUpdate()
. For example:
async performUpdate() {
await new Promise((resolve) => requestAnimationFrame(() => resolve());
super.performUpdate();
}
shouldUpdate
/**
* Implement to override default behavior.
*/
shouldUpdate(changedProperties) { ... }
Params | changedProperties |
Map . Keys are the names of changed properties; Values are the corresponding previous values. |
Returns | Boolean |
If true , update proceeds. Default return value is true . |
Updates? | Yes | Property changes inside this method will trigger an element update. |
Controls whether an update should proceed. Implement shouldUpdate
to specify which property changes should cause updates. By default, this method always returns true.
Example: Customize which property changes should cause updates
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
static get properties() {
return {
prop1: { type: Number },
prop2: { type: Number }
};
}
constructor() {
super();
this.prop1 = 0;
this.prop2 = 0;
}
render() {
return html`
<p>prop1: ${this.prop1}</p>
<p>prop2: ${this.prop2}</p>
<button @click="${() => this.prop1=this.change()}">Change prop1</button>
<button @click="${() => this.prop2=this.change()}">Change prop2</button>
`;
}
/**
* Only update element if prop1 changed.
*/
shouldUpdate(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
console.log(`${propName} changed. oldValue: ${oldValue}`);
});
return changedProperties.has('prop1');
}
change() {
return Math.floor(Math.random()*10);
}
}
customElements.define('my-element', MyElement);
update
Params | changedProperties |
Map . Keys are the names of changed properties; Values are the corresponding previous values. |
Updates? | No | Property changes inside this method do not trigger an element update. |
Reflects property values to attributes and calls render
to render DOM via lit-html. Provided here for reference. You don’t need to override or call this method.
render
/**
* Implement to override default behavior.
*/
render() { ... }
Returns | TemplateResult |
Must return a lit-html TemplateResult . |
Updates? | No | Property changes inside this method will not trigger an element update. |
Uses lit-html to render the element template. You must implement render
for any component that extends the LitElement base class.
See the documentation on Templates for more information.
firstUpdated
/**
* Implement to override default behavior.
*/
firstUpdated(changedProperties) { ... }
Params | changedProperties |
Map . Keys are the names of changed properties; Values are the corresponding previous values. |
Updates? | Yes | Property changes inside this method will trigger an element update. |
Called after the element’s DOM has been updated the first time, immediately before updated
is called.
Implement firstUpdated
to perform one-time work after the element’s template has been created.
Example: Focus an input element on first update
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
static get properties() {
return {
textAreaId: { type: String },
startingText: { type: String }
};
}
constructor() {
super();
this.textAreaId = 'myText';
this.startingText = 'Focus me on first update';
}
render() {
return html`
<textarea id="${this.textAreaId}">${this.startingText}</textarea>
`;
}
firstUpdated(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
console.log(`${propName} changed. oldValue: ${oldValue}`);
});
const textArea = this.shadowRoot.getElementById(this.textAreaId);
textArea.focus();
}
}
customElements.define('my-element', MyElement);
updated
/**
* Implement to override default behavior.
*/
updated(changedProperties) { ... }
Params | changedProperties |
Map . Keys are the names of changed properties; Values are the corresponding previous values. |
Updates? | Yes | Property changes inside this method will trigger an element update. |
Called when the element’s DOM has been updated and rendered. Implement to perform some task after an update.
Example: Focus an element after update
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
static get properties() {
return {
prop1: { type: Number },
prop2: { type: Number }
};
}
constructor() {
super();
this.prop1 = 0;
this.prop2 = 0;
}
render() {
return html`
<style>button:focus { background-color: aliceblue; }</style>
<p>prop1: ${this.prop1}</p>
<p>prop2: ${this.prop2}</p>
<button id="a" @click="${() => this.prop1=Math.random()}">prop1</button>
<button id="b" @click="${() => this.prop2=Math.random()}">prop2</button>
`;
}
updated(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
console.log(`${propName} changed. oldValue: ${oldValue}`);
});
let b = this.shadowRoot.getElementById('b');
b.focus();
}
}
customElements.define('my-element', MyElement);
updateComplete
// Await Promise property.
await this.updateComplete;
Type | Promise |
Resolves with a Boolean when the element has finished updating. |
Resolves |
true if there are no more pending updates.false if this update cycle triggered another update. |
The updateComplete
Promise resolves when the element has finished updating. Use updateComplete
to to wait for an update:
await this.updateComplete;
// do stuff
this.updateComplete.then(() => { /* do stuff */ });
Example
import { LitElement, html } from '@polymer/lit-element';
class MyElement extends LitElement {
static get properties() {
return {
prop1: { type: Number }
};
}
constructor() {
super();
this.prop1 = 0;
}
render() {
return html`
<p>prop1: ${this.prop1}</p>
<button @click="${this.changeProp}">prop1</button>
`;
}
async getMoreState() {
return;
}
async changeProp() {
this.prop1 = Math.random();
await Promise.all(this.updateComplete, this.getMoreState());
console.log('Update complete. Other state completed.');
}
}
customElements.define('my-element', MyElement);
Examples
Control when updates are processed
async performUpdate() {
await new Promise((resolve) => requestAnimationFrame(() => resolve());
super.performUpdate();
}
Customize which property changes should cause an update
shouldUpdate(changedProps) {
return changedProps.has('prop1');
}
Customize what constitutes a property change
Specify hasChanged
for the property. See the Properties documentation.
Manage property changes and updates for object subproperties
Mutations (changes to object subproperties and array items) are not observable. Instead, either rewrite the whole object, or call requestUpdate
after a mutation.
// Option 1: Rewrite whole object, triggering an update
this.prop1 = Object.assign({}, this.prop1, { subProp: 'data' });
// Option 2: Mutate a subproperty, then call requestUpdate
this.prop1.subProp = 'data';
this.requestUpdate();
Update in response to something that isn’t a property change
Call requestUpdate
:
// Request an update in response to an event
this.addEventListener('load-complete', async (e) => {
console.log(e.detail.message);
console.log(await this.requestUpdate());
});
Request an update regardless of property changes
Call requestUpdate()
:
this.requestUpdate();
Request an update for a specific property
Call requestUpdate(propName, oldValue)
:
let oldValue = this.prop1;
this.prop1 = 'new value';
this.requestUpdate('prop1', oldValue);
Do something after the first update
Implement firstUpdated
:
firstUpdated(changedProps) {
console.log(changedProps.get('prop1'));
}
Do something after every update
Implement updated
:
updated(changedProps) {
console.log(changedProps.get('prop1'));
}
Do something when the element next updates
Await the updateComplete
promise:
await this.updateComplete;
// do stuff
this.updateComplete.then(() => {
// do stuff
});
Wait for an element to finish updating
Await the updateComplete
promise:
let done = await updateComplete;
updateComplete.then(() => {
// finished updating
});