Properties
This documentation is a work in progress. It describes prerelease software, and is subject to change.
- Overview
- Declare properties
- Initialize property values
- Configure attributes
- Configure property accessors
- Configure property changes
Overview
LitElement manages your declared properties and their corresponding attributes. By default, LitElement will:
- Ensure that an element update is scheduled when any declared property changes.
- Capture instance values for declared properties. Apply any property values that are set before the browser registers a custom element definition.
- Set up an observed (not reflected) attribute with the lowercased name of each property.
- Handle attribute conversion for properties declared as type
String
,Number
,Boolean
,Array
, andObject
. - Use direct comparison (
oldValue !== newValue
) to test for property changes. - Apply any property options and accessors declared by a superclass.
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:
converter
: Convert between properties and attributes.type
: Use LitElement’s default attribute converter.attribute
: Configure observed attributes.reflect
: Configure reflected attributes.noAccessor
: Whether to set up a default property accessor.hasChanged
: Specify what constitutes a property change.
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} </span>`)}
</p>
<p>prop5:
${Object.keys(this.prop5).map(item =>
html`<span>${item}: ${this.prop5[item]} </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} </span>`)}
</p>
<p>prop5:
${Object.keys(this.prop5).map(item =>
html`<span>${item}: ${this.prop5[item]} </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:
-
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 totrue
. null
orundefined
, set the property tofalse
.
- non-
- For Objects and Arrays, when the attribute is:
- Defined, set the property value to
JSON.parse(attributeValue)
.
- Defined, set the property value to
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
orundefined
, remove the attribute.- Defined and not
null
, set the attribute value toJSON.stringify(propertyValue)
.
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} </span>`)}
</p>
<p>prop5:
${Object.keys(this.prop5).map(item =>
html`<span>${item}: ${this.prop5[item]} </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:
-
If
toAttribute
returnsnull
, the attribute is removed. -
If
toAttribute
returnsundefined
, the attribute is not changed.
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.
-
If
toAttribute
returnsnull
, the attribute is removed. -
If
toAttribute
returnsundefined
, the attribute is not changed. -
If
toAttribute
itself is undefined, the property value is set to the attribute 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 '@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:
- Saves the previous property value.
- Calls your custom setter.
- Requests an update, supplying the property name and its old value to the update lifecycle.
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:
- No update request will be made when
this.myProp = ...
is executed. - The update requested as a result of
this.aProp = ...
will still capturemyProp
’s new value. - The change to
myProp
won’t register in the element update lifecycle.
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:
hasChanged
returnstrue
ifnewVal !== oldVal
.hasChanged
returnsfalse
if both the new and old values areNaN
.
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);