You're viewing docs for an older version of Lit.
For the current version, visit lit.dev.

Properties

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.

Declare properties

Declare your element’s properties using a static properties field, or using decorators:

Properties field

static get properties() {
  return { 
    propertyName: options
  };
}

Decorator (requires TypeScript or Babel)

export class MyElement extends LitElement {
  @property(options) 
  propertyName;

In either case, you can pass an options object to configure features for the property.

Property options

The options object can have the following properties:

attribute

Whether the property is associated with an attribute, or a custom name for the associated attribute. Default: true. See Configure observed attributes. If attribute is false, the converter, reflect and type options are ignored.

converter

A custom converter for converting between properties and attributes. If unspecified, use the default attribute converter.

hasChanged

A function that takes an oldValue and newValue and returns a boolean to indicate whether a property has changed when being set. If unspecified, LitElement uses a strict inequality check (newValue !== oldValue) to determine whether the property value has changed.

noAccessor

Set to true to avoid generating the default property accessor. Default: false.

reflect

Whether property value is reflected back to the associated attribute. Default: false. See Configure reflected attributes.

type

A type hint for converting between properties and attributes. This hint is used by LitElement’s default attribute converter, and is ignored if converter is set. If type is unspecified, behaves like type: String. See Use LitElement’s default attribute converter.

An empty options object is equivalent to specifying the default value for all options.

An options object by another name. This guide uses the descriptive term “options object.” In practice the options object is an instance of PropertyDeclaration, so you’ll see that name if you’re using an IDE, or looking at the API reference. By either name, it’s an object that defines a set of options.

Declare properties in a static properties field

To declare properties in a static properties field:

static get properties() { 
  return { 
    greeting: {type: String},
    data: {attribute: false},
    items: {}
  };
}

An empty option object is equivalent to specifying the default value for all options.

Declared properties are initialized like standard class fields—either in the constructor, or with a field initializer if you’re using decorators.

Example: Declare properties with a static properties field

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

class MyElement extends LitElement {
  static get properties() {
    return {
      greeting: {type: String},
      data: {attribute: false},
      items: {type: Array},
    };
  }

  constructor() {
    super();
    this.greeting = 'Hello';
    this.data = {name: 'Cora'};
    this.items = [1, 2, 3];
  }

  render() {
    return html`
      <p>${this.greeting} ${this.data.name}.</p>
      <p>You have ${this.items.length} items.</p>
    `;
  }
}

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

Code Editor not supported on this browser

Declare properties with decorators

Use the @property decorator to declare properties (instead of the static properties field).

@property({type: String})
mode = 'auto';

@property()
data = {};

The argument to the @property decorator is an options object. Omitting the argument is equivalent to specifying the default value for all options.

Using decorators. Decorators are a proposed JavaScript feature, so you’ll need to use a transpiler like Babel or the TypeScript compiler to use decorators. See Using decorators for details.

There is also an @internalProperty decorator for private or protected properties that should trigger an update cycle. Properties declared with @internalProperty shouldn’t be referenced from outside the component.

@internalProperty()
protected active = false;

The @internalProperty decorator automatically sets attribute to false; the only option you can specify for an internal property is the hasChanged function.

The @internalProperty decorator can serve as a hint to a code minifier that the property name can be changed during minification.

Example: Declare properties with decorators

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

@customElement('my-element')
export class MyElement extends LitElement {
  @property()
  greeting = 'Hello';

  @property({attribute: false})
  data = {name: 'Cora'};

  @property({type: Array})
  items = [1, 2, 3];

  render() {
    return html`
      <p>${this.greeting} ${this.data.name}.</p>
      <p>You have ${this.items.length} items.</p>
    `;
  }
}

Code Editor not supported on this browser

What happens when properties change

A property change can trigger an asynchronous update cycle, which causes the component to re-render its template.

When a property changes, the following sequence occurs:

  1. The property’s setter is called.
  2. The setter calls the property’s hasChanged function. The hasChanged function takes the property’s old and new values, and returns true if the change should trigger an update. (The default hasChanged uses a strict inequality test (oldValue !== newValue) to determine if the property has changed.)
  3. If hasChanged returns true, the setter calls requestUpdate to schedule an update. The update itself happens asynchronously, so if several properties are updated at once, they only trigger a single update.
  4. The component’s update method is called, reflecting changed properties to attributes and re-rendering the component’s templates.

There are many ways to hook into and modify the update lifecycle. For more information, see Lifecycle.

Initialize property values

Typically, you initialize property values in the element constructor.

When using decorators, you can initialize the property value as part of the declaration (equivalent to setting the value in the constructor).

You may want to defer initializing a property if the value is expensive to compute and is not required for the initial render of your component. This is a fairly rare case.

Initialize property values in the element constructor

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

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

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

  // Initialize properties 
  this.greeting = 'Hello';
}

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

Example: Initialize property values in the element constructor

Code Editor not supported on this browser

Initialize property values when using decorators

When using the @property decorator, you can initialize a property as part of the declaration:

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

Example: Initialize property values when using decorators

Code Editor not supported on this browser

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:

  • To observe an attribute (set a property from an attribute), the attribute value must be converted from a string to match the property type.

  • To reflect an attribute (set an attribute from a property), the property value must be converted to a string.

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

  • For Strings, when the attribute is defined, set the property to the attribute value.
  • For Numbers, when the attribute is defined, set the property to Number(attributeValue).
  • For Booleans, when the attribute is:
    • non-null, set the property to true.
    • null or undefined, set the property to false.
  • For Objects and Arrays, when the attribute is:
    • Defined, set the property value to JSON.parse(attributeValue).

Convert from property to attribute

  • For Strings, when the property is:
    • null, remove the attribute.
    • undefined, don’t change the attribute.
    • Defined and not null, set the attribute to the property value.
  • For Numbers, when the property is:
    • null, remove the attribute.
    • undefined, don’t change the attribute.
    • Defined and not null, set the attribute to the property value.
  • For Booleans, when the property is:
    • truthy, create the attribute.
    • falsy, remove the attribute.
  • For Objects and Arrays, when the property is:
    • null or undefined, remove the attribute.
    • Defined and not null, set the attribute value to JSON.stringify(propertyValue).

Example: Use the default converter

import { LitElement, html } from '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([...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 = [...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);

Code Editor not supported on this browser

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:

  • If toAttribute returns null, the attribute is removed.

  • If toAttribute returns undefined, the attribute is not changed.

Example: Configure a custom converter

import { LitElement, html } from '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);

Code Editor not supported on this browser

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

Code Editor not supported on this browser

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.

  • If toAttribute returns null, the attribute is removed.

  • If toAttribute returns undefined, the attribute is not changed.

  • If toAttribute itself is undefined, the attribute value is set to the property value without conversion.

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

Code Editor not supported on this browser

Set property values from attributes in markup

If a property is configured with attribute: true (the default), users can set the property values from observed attributes in static 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.

Attributes versus property bindings. Setting a static attribute value is not the same as binding to a property. See Bind to a property.

Configure property accessors

By default, LitElement generates a getter/setter pair for all declared properties. The setter 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 your own property accessors

To specify how getting and setting works for a property, you can define your getter/setter pair. For example:

static get properties() { return { myProp: { type: String } }; }

set myProp(value) {
  // Implement setter logic here... 
  // retrieve the old property value and store the new one
  this.requestUpdate('myProp', oldValue);
} 
get myProp() { ... }

...

// Later, set the property
this.myProp = 'hi'; // Invokes your setter

If your class defines its own accessors for a property, LitElement will not overwrite them with generated accessors. If your class does not define accessors for a property, LitElement will generate them, even if a superclass has defined the property or accessors.

The setters that LitElement generates automatically call requestUpdate. If you write your own setter you must call requestUpdate manually, supplying the property name and its old value.

Example

A common pattern for accessors is to store the property value using a private property that’s only accessed inside the component. This example uses an underscore prefix (_prop) to identify the private property—you could also use TypeScript’s private or protected keywords.

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

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

  set prop(val) {
    let oldVal = this._prop;
    this._prop = Math.floor(val);
    this.requestUpdate('prop', oldVal);
  }

  get prop() { return this._prop; }

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

  render() {
    return html`
      <p>prop: ${this.prop}</p>
      <button @click="${() =>  { this.prop = Math.random()*10; }}">
        change prop
      </button>
    `;
  }
}
customElements.define('my-element', MyElement);

If you want to use your own property accessor with the @property decorator, you can achieve this by putting the decorator on the getter:

   private _myProp: string = '';

  @property({ type: String })
  get myProp(): string {
    return this._myProp;
  }
  set myProp(value: string) {
    const oldValue = this._myProp;
    this._myProp = value;
    this.requestUpdate('myProp', oldValue);
  }

Prevent LitElement from generating a property accessor

In rare cases, a subclass may need to change or add property options for a property that exists on its superclass.

To prevent LitElement from generating a property accessor that overwrites the superclass’s defined accessor, set noAccessor to true in the property declaration:

static get properties() { 
  return { myProp: { type: Number, noAccessor: true } }; 
}

You don’t need to set noAccessor when defining your own accessors.

Example

Subclass element

import { SuperElement } from './super-element.js';

class SubElement extends SuperElement {  
  static get properties() { 
    return { prop: { reflect: true, noAccessor: true } };
  }
}

customElements.define('sub-element', SubElement);

Code Editor not supported on this browser

Configure property changes

All declared properties have a function, hasChanged, which is called when 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:

  • hasChanged returns true if newVal !== oldVal.
  • hasChanged returns false if both the new and old values are NaN.

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
}}

hasChanged may not be called for every change. If a property’s hasChanged returns true once, it won’t be called again until after the next update, even if the property is changed multiple times. If you want to be notified each time a property is set, you should create a custom setter for the property, as described in Create your own property accessors.

Example: Configure property changes

import { LitElement, html } from '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);

Code Editor not supported on this browser