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:

  1. A property is set.
  2. The property’s hasChanged function evaluates whether the property has changed.
  3. If the property has changed, requestUpdate fires, scheduling an update.
  4. shouldUpdate checks whether the update should proceed.
  5. If the update should proceed, the update function reflects the element’s properties to its attributes.
  6. The lit-html render function renders DOM changes.
  7. The updated function is called, exposing the properties that changed.
  8. The updateComplete Promise resolves. Any code waiting for it can now run.

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.

LitElement updates are requested asynchronously as Promises, and are queued as microtasks. This means that updates are processed at the end of every iteration of the event loop, making updates fast and responsive.

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:

  1. someProperty.hasChanged
  2. requestUpdate
  3. shouldUpdate
  4. update
  5. render
  6. firstUpdated
  7. updated
  8. 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 { 
    foo: String 
  };}
  
  /**
   * Custom property setter for `foo`. 
   * 
   * Call `requestUpdate` when implementing a custom property setter
   * to ensure that changes to the property will trigger updates
   * when required.
   * 
   * Pass the old value of the property to `requestUpdate` so that 
   * any property options can be correctly applied.
   */
  set foo(newVal) { 
    let oldVal = this.foo;
    console.log('setting foo from', oldVal, 'to', newVal);
    this.setAttribute('foo', newVal);
    this.requestUpdate('foo', oldVal).then(
      result => console.log('updateComplete:', result)
    );
  }

  /**
   * Custom property getter for `foo`. 
   */ 
  get foo() {
    return this.getAttribute('foo'); 
  }

  render() { 
    return html`
      ${this.foo}
      <button @click="${this.getNewVal}">get new value</button>
    `;
  }
  
  getNewVal() {
    let newVal = Math.floor(Math.random()*10);
    this.foo = newVal;
  }
}
customElements.define('my-element', MyElement);

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: Number,
      prop2: 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

import { LitElement, html } from '@polymer/lit-element';

class MyElement extends LitElement {  
  static get properties(){
    return {
      textAreaId: String,
      startingText: 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: Number,
      prop2: 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 */ });

To have updateComplete await additional state before it resolves, implement the updateComplete getter:

  get updateComplete() {
    return this.getMoreState().then(() => {
      return this._updatePromise;
    });
  }

Example

import { LitElement, html } from '@polymer/lit-element';

class MyElement extends LitElement {  
  static get properties() {
    return {
      prop1: Number
    };
  }

  constructor() {
    super();
    this.prop1 = 0;
  }

  render() {
    return html`      
      <p>prop1: ${this.prop1}</p>
      <button @click="${this.changeProp}">prop1</button>
    `;
  }

  /**
   * Wait for additional state before completion of every update
   */
  get updateComplete() {
    console.log('Waiting for additional state...');
    return this.getMoreState().then(() => {
      console.log('Additional state.');
      return this._updatePromise;
    });
  }

  async getMoreState() {
    return;
  }

  async changeProp() {
    this.prop1 = Math.random();
    console.log('updateComplete resolved: ', await this.updateComplete);
  }
}

customElements.define('my-element', MyElement);

Examples

Customize which property changes should cause an update

Implement shouldUpdate:

shouldUpdate(changedProps) {
  return changedProps.has('prop1');
}

Customize what constitutes a property change

Specify hasChanged for the property:

static get properties(){ return {
  myProp: {
    type: Number,
    /* Only consider myProp to have changed if newVal > oldVal */
    hasChanged(newVal, oldVal) {
      return newVal > oldVal;
    }
  }
}

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 updateComplete;
// do stuff
updateComplete.then(() => {
  // do stuff
});

Wait for an element to finish updating

Await the updateComplete promise:

let done = await updateComplete;
updateComplete.then(() => {
  // finished updating
});