Properties

This documentation is a work in progress. It describes prerelease software, and is subject to change.

Overview

LitElement manages your declared properties and their corresponding attributes. By default, LitElement will:

Remember to declare all of the properties that you want LitElement to manage. For the property features above to be applied, you must declare the property.

Property options

A property declaration is an object in the following format:

{ optionName1: optionValue1, optionName2: optionValue2, ... }

The following options are available:

All property declaration options can be specified in a static properties getter, or with TypeScript decorators.

Declare properties

Declare your element’s properties by implementing a static properties getter, or by using TypeScript decorators:

// properties getter
static get properties() {
  return { 
    prop1: { type: String }
  };
}
// TypeScript decorators
export class MyElement extends LitElement {
  @property( { type : String }  ) prop1 = '';

Declare properties in a static properties getter

To declare properties in a static properties getter:

static get properties() { 
  return { 
    prop1: { type: String },
    prop2: { type: Number },
    prop3: { type: Boolean }
  };
}

If you implement a static properties getter, initialize your property values in the element constructor.

constructor() {
  // Always call super() first
  super();
  this.prop1 = 'Hello World';
  ...
}

Remember to call super() first in your constructor, or your element won’t render at all.

Example: Declare properties with a static properties getter

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

class MyElement extends LitElement {  
  static get properties() { return { 
    prop1: { type: String },
    prop2: { type: Number },
    prop3: { type: Boolean },
    prop4: { type: Array },
    prop5: { type: Object }
  };}

  constructor() {
    super();
    this.prop1 = 'Hello World';
    this.prop2 = 5;
    this.prop3 = false;
    this.prop4 = [1,2,3];
    this.prop5 = { subprop1: 'prop 5 subprop1 value' }
  }

  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <p>prop3: ${this.prop3}</p>
      <p>prop4[0]:</p>${this.prop4[0]}</p>
      <p>prop5.subprop1: ${this.prop5.subprop1}</p>
    `;
  }
}

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

Declare properties with TypeScript decorators

You can also declare properties with TypeScript decorators:

@property({type : String})  prop1 = 'Hello World';

Example: Declare properties with TypeScript decorators

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

@customElement('my-element')
export class MyElement extends LitElement {
  @property({type : String})  prop1 = 'Hello World';
  @property({type : Number})  prop2 = 5;
  @property({type : Boolean}) prop3 = true;
  @property({type : Array})   prop4 = [1,2,3];
  @property({type : Object})  prop5 = { subprop1: 'prop 5 subprop1 value' };

  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <p>prop3: ${this.prop3}</p>
      <p>prop4[0]:</p>${this.prop4[0]}</p>
      <p>prop5.subprop1: ${this.prop5.subprop1}</p>
    `;
  }
}
customElements.define('my-element', MyElement);

Initialize property values

Initialize property values in the element constructor

If you implement a static properties getter, initialize your property values in the element constructor:

static get properties() { return { /* Property declarations */ }; } 

constructor() {
  // Always call super() first
  super();

  // Initialize properties 
  this.prop1 = 'Hello World';
}

Remember to call super() first in your constructor, or your element won’t render at all.

Example: Initialize property values in the element constructor

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

class MyElement extends LitElement {  
  static get properties() { return { 
    prop1: { type: String },
    prop2: { type: Number },
    prop3: { type: Boolean },
    prop4: { type: Array },
    prop5: { type: Object }
  };}

  constructor() {
    super();
    this.prop1 = 'Hello World';
    this.prop2 = 5;
    this.prop3 = true;
    this.prop4 = [1,2,3];
    this.prop5 = { stuff: 'hi', otherStuff: 'wow' };
  }
  
  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <p>prop3: ${this.prop3}</p>

      <p>prop4: ${this.prop4.map((item, index) => 
        html`<span>[${index}]:${item}&nbsp;</span>`)}
      </p>
      
      <p>prop5: 
        ${Object.keys(this.prop5).map(item => 
          html`<span>${item}: ${this.prop5[item]}&nbsp;</span>`)}
      </p>
    `;
  }
}
customElements.define('my-element', MyElement);

Initialize property values when using TypeScript decorators

TypeScript users can initialize property values when they are declared with the @property decorator:

@property({ type : String }) prop1 = 'Hello World';

Example: Initialize property values when using TypeScript decorators

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

@customElement('my-element')
export class MyElement extends LitElement {
  // Declare and initialize properties
  @property({type : String})  prop1 = 'Hello World';
  @property({type : Number})  prop2 = 5;
  @property({type : Boolean}) prop3 = true;
  @property({type : Array})   prop4 = [1,2,3];
  @property({type : Object})  prop5 = { subprop1: 'hi', thing: 'fasdfsf' };
  
  render() {
    return html`
      <p>prop1: ${this.prop1}</p>
      <p>prop2: ${this.prop2}</p>
      <p>prop3: ${this.prop3}</p>

      <p>prop4: ${this.prop4.map((item, index) => 
        html`<span>[${index}]:${item}&nbsp;</span>`)}
      </p>
      
      <p>prop5: 
        ${Object.keys(this.prop5).map(item => 
          html`<span>${item}: ${this.prop5[item]}&nbsp;</span>`)}
      </p>
    `;
  }
}

Initialize property values from attributes in markup

You can also initialize property values from observed attributes in markup:

index.html

<my-element 
  mystring="hello world"
  mynumber="5"
  mybool
  myobj='{"stuff":"hi"}'
  myarray='[1,2,3,4]'></my-element>

See observed attributes and converting between properties and attributes for more information on setting up initialization from attributes.

Configure attributes

Convert between properties and attributes

While element properties can be of any type, attributes are always strings. This impacts the observed attributes and reflected attributes of non-string properties:

Use the default converter

LitElement has a default converter which handles String, Number, Boolean, Array, and Object property types.

To use the default converter, specify the type option in your property declaration:

// Use LitElement's default converter 
prop1: { type: String },
prop2: { type: Number },
prop3: { type: Boolean },
prop4: { type: Array },
prop5: { type: Object }

The information below shows how the default converter handles conversion for each type.

Convert from attribute to property

Convert from property to attribute

Example: Use the default converter

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

class MyElement extends LitElement { 
  static get properties() { return {  
    prop1: { type: String, reflect: true },
    prop2: { type: Number, reflect: true },
    prop3: { type: Boolean, reflect: true },
    prop4: { type: Array, reflect: true },
    prop5: { type: Object, reflect: true }
  };}

  constructor() {
    super();
    this.prop1 = '';
    this.prop2 = 0;
    this.prop3 = false;
    this.prop4 = [];
    this.prop5 = { };
  }

  attributeChangedCallback(name, oldval, newval) {
    console.log('attribute change: ', name, newval);
    super.attributeChangedCallback(name, oldval, newval);
  }

  render() {
    return html`
      <p>prop1 ${this.prop1}</p>
      <p>prop2 ${this.prop2}</p>
      <p>prop3 ${this.prop3}</p>
      
      <p>prop4: ${this.prop4.map((item, index) => 
        html`<span>[${index}]:${item}&nbsp;</span>`)}
      </p>
      
      <p>prop5: 
        ${Object.keys(this.prop5).map(item => 
          html`<span>${item}: ${this.prop5[item]}&nbsp;</span>`)}
      </p>

      <button @click="${this.changeProperties}">change properties</button>
      <button @click="${this.changeAttributes}">change attributes</button>
    `;
  }

  changeAttributes() {
    let randy = Math.floor(Math.random()*10);
    let myBool = this.getAttribute('prop3');

    this.setAttribute('prop1', randy.toString);
    this.setAttribute('prop2', randy.toString);
    this.setAttribute('prop3', myBool? '' : null);
    this.setAttribute('prop4', 
      JSON.stringify(Object.assign([], [...this.prop4], randy)));
    this.setAttribute('prop5', 
      JSON.stringify(Object.assign({}, this.prop5, {[randy]: randy})));
    this.requestUpdate();
  }

  changeProperties() {
    let randy = Math.floor(Math.random()*10);
    let myBool = this.prop3;

    this.prop1 = randy.toString();
    this.prop2 = randy; 
    this.prop3 = !myBool;
    this.prop4 = Object.assign([], [...this.prop4], randy);
    this.prop5 = Object.assign({}, this.prop5, {[randy]: randy});
  }

  updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => { 
      console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
  }

}

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

Configure a custom converter

You can specify a custom property converter in your property declaration with the converter option:

myProp: { 
  converter: // Custom property converter
} 

converter can be an object or a function. If it is an object, it can have keys for fromAttribute and toAttribute:

prop1: { 
  converter: { 
    fromAttribute: (value, type) => { 
      // `value` is a string
      // Convert it to a value of type `type` and return it
    },
    toAttribute: (value, type) => { 
      // `value` is of type `type` 
      // Convert it to a string and return it
    }
  }
}

If converter is a function, it is used in place of fromAttribute:

myProp: { 
  converter: (value, type) => { 
    // `value` is a string
    // Convert it to a value of type `type` and return it
  }
} 

If no toAttribute function is supplied for a reflected attribute, the attribute is set to the property value without conversion.

During an update:

Example: Configure a custom converter

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

class MyElement extends LitElement { 
  static get properties() { return {  
    myProp: { 
      reflect: true,
      converter: { 
        toAttribute(value) {
          console.log('myProp\'s toAttribute.');
          console.log('Processing:', value, typeof(value));
          let retVal = String(value);
          console.log('Returning:', retVal, typeof(retVal));
          return retVal;
        },

        fromAttribute(value) {
          console.log('myProp\'s fromAttribute.');
          console.log('Processing:', value, typeof(value));
          let retVal = Number(value);
          console.log('Returning:', retVal, typeof(retVal));
          return retVal;
        }
      }
    },
    
    theProp: { 
      reflect: true,
      converter(value) {
        console.log('theProp\'s converter.');
        console.log('Processing:', value, typeof(value));

        let retVal = Number(value);
        console.log('Returning:', retVal, typeof(retVal));
        return retVal;
      }},
  };}

  constructor() {
    super();
    this.myProp = 'myProp';
    this.theProp = 'theProp'; 
  }

  attributeChangedCallback(name, oldval, newval) {
    // console.log('attribute change: ', name, newval);
    super.attributeChangedCallback(name, oldval, newval);
  }

  render() {
    return html`
      <p>myProp ${this.myProp}</p>
      <p>theProp ${this.theProp}</p>

      <button @click="${this.changeProperties}">change properties</button>
      <button @click="${this.changeAttributes}">change attributes</button>
    `;
  }

  changeAttributes() {
    let randomString = Math.floor(Math.random()*100).toString();
    this.setAttribute('myprop', 'myprop ' + randomString);
    this.setAttribute('theprop', 'theprop ' + randomString);
    this.requestUpdate();
  }

  changeProperties() {
    let randomString = Math.floor(Math.random()*100).toString();
    this.myProp='myProp ' + randomString;
    this.theProp='theProp ' + randomString;
  }
}
customElements.define('my-element', MyElement);

Configure observed attributes

An observed attribute fires the custom elements API callback attributeChangedCallback whenever it changes. By default, whenever an attribute fires this callback, LitElement sets the property value from the attribute using the property’s fromAttribute function. See Convert between properties and attributes for more information.

By default, LitElement creates a corresponding observed attribute for all declared properties. The name of the observed attribute is the property name, lowercased:

// observed attribute name is "myprop"
myProp: { type: Number }

To create an observed attribute with a different name, set attribute to a string:

// Observed attribute will be called my-prop
myProp: { attribute: 'my-prop' }

To prevent an observed attribute from being created for a property, set attribute to false. The property will not be initialized from attributes in markup, and attribute changes won’t affect it.

// No observed attribute for this property
myProp: { attribute: false }

An observed attribute can be used to provide an initial value for a property via markup. See Initialize properties with attributes in markup.

Example: Configure observed attributes

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

class MyElement extends LitElement { 
  static get properties() { return {  
    myProp: { attribute: true },
    theProp: { attribute: false },
    otherProp: { attribute: 'other-prop' },
  };}

  constructor() {
    super();
    this.myProp = 'myProp';
    this.theProp = 'theProp'; 
    this.otherProp = 'otherProp';
  }

  attributeChangedCallback(name, oldval, newval) {
    console.log('attribute change: ', name, newval);
    super.attributeChangedCallback(name, oldval, newval);
  }

  render() {
    return html`
      <p>myProp ${this.myProp}</p>
      <p>theProp ${this.theProp}</p>
      <p>otherProp ${this.otherProp}</p>

      <button @click="${this.changeAttributes}">change attributes</button>
    `;
  }

  changeAttributes() {
    let randomString = Math.floor(Math.random()*100).toString();
    this.setAttribute('myprop', 'myprop ' + randomString);
    this.setAttribute('theprop', 'theprop ' + randomString);
    this.setAttribute('other-prop', 'other-prop ' + randomString);
    this.requestUpdate();
  }
  
  updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => { 
      console.log(`${propName} changed. oldValue: ${oldValue}`);
    });
  }
}
customElements.define('my-element', MyElement);

Configure reflected attributes

You can configure a property so that whenever it changes, its value is reflected to its observed attribute. For example:

// Value of property "myProp" will reflect to attribute "myprop"
myProp: { reflect: true }

When the property changes, LitElement uses the toAttribute function in the property’s converter to set the attribute value from the new property value.

LitElement tracks reflection state during updates. LitElement keeps track of state information to avoid creating an infinite loop of changes between a property and an observed, reflected attribute.

Example: Configure reflected attributes

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

class MyElement extends LitElement { 
  static get properties() { return {  
    myProp: { reflect: true }
  };}

  constructor() {
    super();
    this.myProp='myProp';
  }

  attributeChangedCallback(name, oldval, newval) {
    console.log('attribute change: ', newval);
    super.attributeChangedCallback(name, oldval, newval);
  }

  render() {
    return html`
      <p>${this.myProp}</p>
      
      <button @click="${this.changeProperty}">change property</button>
    `;
  }

  changeProperty() {
    let randomString = Math.floor(Math.random()*100).toString();
    this.myProp='myProp ' + randomString;
  }
  
}
customElements.define('my-element', MyElement);

Configure property accessors

By default, LitElement generates a property accessor for all declared properties. The accessor is invoked whenever you set the property:

// Declare a property
static get properties() { return { myProp: { type: String } }; }
...
// Later, set the property
this.myProp = 'hi'; // invokes myProp's generated property accessor

Generated accessors automatically call requestUpdate, initiating an update if one has not already begun.

Create custom property accessors

To specify how getting and setting works for a property, create custom accessors:

// Declare a property
static get properties() { return { myProp: { type: String } }; }

// Custom accessors
set myProp(value) { ... /* Custom setter */ } 
get myProp() { ... /* Custom getter */ }

...

// Later, set the property
this.myProp = 'hi'; // Invokes generated accessor, which calls custom accessor

When you create custom property accessors for a property, LitElement still generates its own accessors unless you specify otherwise (see below). The generated setter:

Prevent LitElement from generating a property accessor

To prevent LitElement from generating property accessors, set noAccessors to true in the property declaration:

static get properties() { return { 
  // Don't generate accessors for myProp
  myProp: { type: Number, noAccessors: true } 

  // Do generate accessors for aProp
  aProp: { type: String }
}; }

// Create custom accessors for myProp
set myProp(value) { this._myProp = Math.floor(value); } 
get myProp() { return this._myProp; }

updated(changedProperties) { ... /* no changedProperties entry for myProp */ }

...
// later...
this.myProp = Math.random()*10; // Invokes custom setter; no generated setter
this.aProp = 'hi'; // Invokes generated setter

In the example above:

To handle update requests and property options in a custom setter, call this.requestUpdate('propertyName', oldValue):

set myProp(value) { 
  let oldValue = this._myProp;
  this._myProp = Math.floor(value); 
  this.requestUpdate('myProp', oldValue);
} 

Example: Custom property accessors

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);

Configure property changes

All declared properties have a function, hasChanged, which is called whenever the property is set.

hasChanged compares the property’s old and new values, and evaluates whether or not the property has changed. If hasChanged returns true, LitElement starts an element update if one is not already scheduled. See the Element update lifecycle documentation for more information on how updates work.

By default:

To customize hasChanged for a property, specify it as a property option:

myProp: { hasChanged(newVal, oldVal) {
  // compare newVal and oldVal
  // return `true` if an update should proceed
}}

Example: Configure property changes

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

class MyElement extends LitElement {  
  static get properties(){ return {
    myProp: {
      type: Number,

      /**
       * Compare myProp's new value with its old value. 
       * 
       * Only consider myProp to have changed if newVal is larger than
       * oldVal.
       */
      hasChanged(newVal, oldVal) {
        if (newVal > oldVal) {
          console.log(`${newVal} > ${oldVal}. hasChanged: true.`);
          return true;
        }
        else {
          console.log(`${newVal} <= ${oldVal}. hasChanged: false.`);
          return false;
        }
      }
    }};
  }
  
  constructor(){
    super();
    this.myProp = 1;
  }

  render(){
    return html`
      <p>${this.myProp}</p>
      <button @click="${this.getNewVal}">get new value</button>
    `;
  }
  
  updated(){
    console.log('updated');
  }

  getNewVal(){
    let newVal = Math.floor(Math.random()*10);
    this.myProp = newVal;
  }

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