Properties
- Overview
- Declare properties
- What happens when properties change
- 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.
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, theconverter
,reflect
andtype
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
andnewValue
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. Iftype
is unspecified, behaves liketype: String
. See Use LitElement’s default attribute converter. - The property’s setter is called.
- The setter calls the property’s
hasChanged
function. ThehasChanged
function takes the property’s old and new values, and returns true if the change should trigger an update. (The defaulthasChanged
uses a strict inequality test (oldValue !== newValue
) to determine if the property has changed.) - If
hasChanged
returns true, the setter callsrequestUpdate
to schedule an update. The update itself happens asynchronously, so if several properties are updated at once, they only trigger a single update. - The component’s
update
method is called, reflecting changed properties to attributes and re-rendering the component’s templates. -
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.
- 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
- 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)
.
-
If
toAttribute
returnsnull
, the attribute is removed. -
If
toAttribute
returnsundefined
, the attribute is not changed. -
If
toAttribute
returnsnull
, the attribute is removed. -
If
toAttribute
returnsundefined
, the attribute is not changed. -
If
toAttribute
itself is undefined, the attribute value is set to the property value without conversion. hasChanged
returnstrue
ifnewVal !== oldVal
.hasChanged
returnsfalse
if both the new and old values areNaN
.
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);
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>
`;
}
}
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:
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
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
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 '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([...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);
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 '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 '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 '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);
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);
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:
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);