Declared properties
Table of contents
You can declare properties on your custom element by adding them to
the properties
object on your prototype. Adding a property to the properties
object allows a user to configure the property from markup (see
attribute deserialization for details).
Any property that’s part of your element’s public API should be declared in the
properties
object.
In addition, the properties
object can be used to specify:
- Property type.
- Default value.
- Property change observer. Calls a method whenever the property value changes.
- Read-only status. Prevents accidental changes to the property value.
- Two-way data binding support. Fires an event whenever the property value changes.
- Computed property. Dynamically calculates a value based on other properties.
- Property reflection to attribute. Updates the corresponding attribute value when the property value changes.
Example:
Polymer({
is: 'x-custom',
properties: {
user: String,
isHappy: Boolean,
count: {
type: Number,
readOnly: true,
notify: true
}
},
ready: function() {
this.textContent = 'Hello World, I am a Custom Element!';
}
});
The properties
object supports the following keys for each property:
Key | Details |
---|---|
type |
Type: constructor (one of Boolean , Date , Number , String , Array or Object )Attribute type, used for deserializing from an attribute. Unlike 0.5, the property's type is explicit, specified using the type's constructor. See attribute deserialization for more information. |
value |
Type: boolean , number , string or function .Default value for the property. If value is a function, the function is
invoked and the return value is used as the default value of the property. If
the default value should be an array or object unique to the instance, create
the array or object inside a function. See
Configuring default property values for more information.
|
reflectToAttribute |
Type: boolean Set to true to cause the corresponding attribute to be set on the host node
when the property value changes. If the property value is Boolean, the attribute
is created as a standard HTML boolean attribute (set if true, not set if false).
For other property types, the attribute value is a string representation of the
property value. Equivalent to reflect in Polymer 0.5.
See Reflecting properties to attributes for
more information.
|
readOnly |
Type: boolean If true , the property can't be set directly by assignment or data binding. See Read-only properties.
|
notify |
Type: boolean If true , the property is available for two-way data binding. In addition, an
event, property-name-changed is fired whenever the
property changes. See Property change notification events (notify)
for more information.
|
computed |
Type: string The value is interpreted as a method name and argument list. The method is invoked to calculate the value whenever any of the argument values changes. Computed properties are always read-only. See Computed properties for more information. |
observer |
Type: string The value is interpreted as a method name to be invoked when the property value changes. Note that unlike in 0.5, property change handlers must be registered explicitly. The propertyNameChanged method will not be
invoked automatically. See Property change callbacks (observers)
for more information.
|
Property name to attribute name mapping
For data binding, deserializing properties from attributes, and reflecting properties back to attributes, Polymer maps attribute names to property names and the reverse.
When mapping attribute names to property names:
-
Attribute names are converted to lowercase property names. For example, the attribute
firstName
maps tofirstname
. -
Attribute names with dashes are converted to camelCase property names by capitalizing the character following each dash, then removing the dashes. For example, the attribute
first-name
maps tofirstName
.
The same mappings happen in reverse when converting property names to attribute
names (for example, if a property is defined using reflectToAttribute: true
.)
Compatibility note: In 0.5, Polymer attempted to map attribute names to corresponding properties.
For example, the attribute foobar
would map to the property fooBar
if it was
defined on the element. This does not happen in 1.0—attribute to property
mappings are set up on the element at registration time based on the rules
described above.
Attribute deserialization
If a property is configured in the properties
object, an attribute on the
instance matching the property name will be deserialized according to the type
specified and assigned to a property of the same name on the element instance.
If no other properties
options are specified for a property, the type
(specified using the type constructor, e.g. Object
, String
, etc.) can be set
directly as the value of the property in the properties
object; otherwise it
should be provided as the value to the type
key in the properties
configuration object.
The type system includes support for Boolean and Number values, Object and Array values expressed as JSON, or Date objects expressed as any Date-parsable string representation.
Boolean properties are set based on the presence of the attribute:
if the attribute exists at all, the property is set to true
, regardless
of the attribute value. If the attribute is absent, the property
gets its default value.
Example:
<script>
Polymer({
is: 'x-custom',
properties: {
user: String,
manager: {
type: Boolean,
notify: true
}
},
attached: function() {
// render
this.textContent = 'Hello World, my user is ' + (this.user || 'nobody') + '.\n' +
'This user is ' + (this.manager ? '' : 'not') + ' a manager.';
}
});
</script>
<x-custom user="Scott" manager></x-custom>
<!--
<x-custom>'s text content becomes:
Hello World, my user is Scott.
This user is a manager.
-->
In order to configure camel-case properties of elements using attributes, dash- case should be used in the attribute name. Example:
<script>
Polymer({
is: 'x-custom',
properties: {
userName: String
}
});
</script>
<x-custom user-name="Scott"></x-custom>
<!-- Sets <x-custom>.userName = 'Scott'; -->
Note: Deserialization occurs both at create time, and at runtime (for
example, when the attribute is changed using setAttribute
). However, it is
encouraged that attributes only be used for configuring properties in static
markup, and instead that properties are set directly for changes at runtime.
Configuring boolean properties
For a Boolean property to be configurable from markup, it must default to false
. If it defaults to true
, you cannot set it to false
from markup, since the presence of the attribute, with or without a value, equates to true
. This is the standard behavior for attributes in the web platform.
If this behavior doesn’t fit your use case, you can use a string-valued or number-valued attribute instead.
Configuring object and array properties
For object and array properties you can pass an object or array in JSON format:
<my-element book='{ "title": "Persuasion", "author": "Austen" }'></my-element>
Note that JSON requires double quotes, as shown above.
Configuring default property values
Default values for properties may be specified in the properties
object using
the value
field. The value may either be a primitive value, or a function
that returns a value.
If you provide a function, Polymer calls the function once per element instance.
When initializing a property to an object or array value, use a function to ensure that each element gets its own copy of the value, rather than having an object or array shared across all instances of the element.
Example:
Polymer({
is: 'x-custom',
properties: {
mode: {
type: String,
value: 'auto'
},
data: {
type: Object,
notify: true,
value: function() { return {}; }
}
}
});
Property change observers
Custom element properties may be observed for changes by specifying observer
property in the properties
object for the property that gives the name of a function
to call. When the property changes, the change handler will be called with the
new and old values as arguments.
Example:
Polymer({
is: 'x-custom',
properties: {
disabled: {
type: Boolean,
observer: '_disabledChanged'
},
highlight: {
observer: '_highlightChanged'
}
},
_disabledChanged: function(newValue, oldValue) {
this.toggleClass('disabled', newValue);
this.highlight = true;
},
_highlightChanged: function() {
this.classList.add('highlight');
this.async(function() {
this.classList.remove('highlight');
}, 300);
}
});
Warning: A single property observer shouldn’t rely on any other properties, sub-properties, or paths because the observer can be called while these dependencies are undefined. See Always include dependencies as observer arguments for details.
Property change observation is achieved in Polymer by installing setters on the
custom element prototype for properties with registered interest (as opposed to
observation via Object.observe
or dirty checking, for example).
Observing changes to multiple properties
To observe changes to a set of properties, use the observers
array.
These observers differ from single-property observers in a few ways:
- Observers are not invoked until all dependent properties are defined (
!== undefined
). So each dependent properties should have a defaultvalue
defined inproperties
(or otherwise be initialized to a non-undefined
value) to ensure the observer is called. - Observers do not receive
old
values as arguments, only new values. Only single-property observers defined in theproperties
object receive bothold
andnew
values.
Example:
Polymer({
is: 'x-custom',
properties: {
preload: Boolean,
src: String,
size: String
},
observers: [
'updateImage(preload, src, size)'
],
updateImage: function(preload, src, size) {
// ... do work using dependent values
}
});
In addition to properties, observers can also observe paths to sub-properties, paths with wildcards, or array changes.
Observing sub-property changes
To observe changes in object sub-properties:
- Define an
observers
array. - Add an item to the
observers
array. The item must be a method name followed by a comma-separated list of one or more paths. For example,onNameChange(dog.name)
for one path, oronNameChange(dog.name, cat.name)
for multiple paths. Each path is a sub-property that you want to observe. - Define the method in your element prototype. When the method is called, the argument to the method is the new value of the sub-property.
In order for Polymer to properly detect the sub-property change, the sub-property must be updated in one of the following two ways:
- Via a property binding.
- By calling
set
.
Example:
<dom-module id="x-sub-property-observer">
<template>
<!-- Sub-property is updated via property binding. -->
<input value="{{user.name::input}}">
</template>
<script>
Polymer({
is: 'x-sub-property-observer',
properties: {
user: {
type: Object,
value: function() {
return {};
}
}
},
// Each item of observers array is a method name followed by
// a comma-separated list of one or more paths.
observers: [
'userNameChanged(user.name)'
],
// Each method referenced in observers must be defined in
// element prototype. The argument to the method is the new value
// of the sub-property.
userNameChanged: function(name) {
console.log('new name: ' + name);
},
});
</script>
</dom-module>
Observe array mutations
Use an array mutation observer to call an observer function whenever an array
item is added or deleted via push
, pop
, shift
, unshift
, or splice
.
Whenever the array is mutated, the observer receives a change record
representing the mutation as a set of array splices.
In many cases, you’ll want to observe both array mutations and changes to sub-properties of array items, in which case you should use a deep sub-property observer.
Never use the built-in JavaScript array methods to splice your arrays. Always use Polymer’s array mutation methods. These methods ensure that elements with registered interest in the array splices are properly notified.
To create a splice observer, specify a path to an array followed by .splices
in your observers
array.
observers: [
'usersAddedOrRemoved(users.splices)'
]
Your observer method should accept a single argument. When your observer method is called, it receives a change record of the mutations that occurred on the array. Each change record provides the following property:
-
indexSplices
. The set of changes that occurred to the array, in terms of array indexes. EachindexSplices
record contains the following properties:index
. Position where the splice started.removed
. Array ofremoved
items.addedCount
. Number of new items inserted atindex
.object
: A reference to the array in question.type
: The string literal ‘splice’.
Change record may be undefined. The change record may be undefined the first time the observer is invoked, so your code should guard against this, as shown in the example.
Example:
Polymer({
is: 'x-custom',
properties: {
users: {
type: Array,
value: function() {
return [];
}
}
},
observers: [
'usersAddedOrRemoved(users.splices)'
],
usersAddedOrRemoved: function(changeRecord) {
if (changeRecord) {
changeRecord.indexSplices.forEach(function(s) {
s.removed.forEach(function(user) {
console.log(user.name + ' was removed');
});
for (var i=0; i<s.addedCount; i++) {
var index = s.index + i;
var newUser = s.object[index];
console.log('User ' + newUser.name + ' added at index ' + index);
}
}, this);
}
},
ready: function() {
this.push('users', {name: "Jack Aubrey"});
},
});
Track key splices
In some situtations, you may need to know about the immutable opaque keys that Polymer uses to track array items. This is a advanced use case, only required if you’re implementing an element like the template repeater.
You can register interest in key additions and deletions by retrieving the array’s collection object:
Polymer.Collection.get(array);
If you’ve registered interest, the change record includes an additional property:
-
keySplices
. The set of changes that occurred to the array in terms of array keys. EachkeySplices
record contains the following properties:added
. Array of added keys.removed
. Array of removed keys.
Template repeaters and key splices. The template repeater (dom-repeat
) element
uses keys internally, so if an array is used by a dom-repeat
, observers
for that array receive the keySplices
property.
Deep sub-property observation
To call an observer when any (deep) sub-property of an
object or array changes, specify a path with a wildcard (*
).
When you specify a path with a wildcard, the argument passed to your observer is a change record object with the following properties:
path
. Path to the property that changed. Use this to determine whether a property changed, a sub-property changed, or an array was mutated.value
. New value of the path that changed.base
. The object matching the non-wildcard portion of the path.
For array mutations, path
is the path to the array that changed,
followed by .splices
. And the change record includes the indexSplices
and
keySplices
properties described in
Observe array mutations.
Example:
<dom-module id="x-deep-observer">
<template>
<input value="{{user.name.first::input}}"
placeholder="First Name">
<input value="{{user.name.last::input}}"
placeholder="Last Name">
</template>
<script>
Polymer({
is: 'x-deep-observer',
properties: {
user: {
type: Object,
value: function() {
return {'name':{}};
}
}
},
observers: [
'userNameChanged(user.name.*)'
],
userNameChanged: function(changeRecord) {
console.log('path: ' + changeRecord.path);
console.log('value: ' + changeRecord.value);
},
});
</script>
</dom-module>
Deep sub-property changes on array items
When a sub-property of an array is modified, changeRecord.path
references
the “key” of the array item that was modified, not the array index. For
example:
console.log(changeRecord.path); // users.#0.name
#0
signifies the key of this example array item. All keys are prefixed
with a number sign (#
) by convention to distinguish them from array indexes.
Keys provide stable references to array items, regardless of any splices
(additions or removals) on the array.
Use the get
method to retrieve an item by path.
var item = this.get('users.#0');
If you need a reference to the index of an array item, you
can retrieve it using indexOf
:
var item = this.get('users.#0');
var index = this.users.indexOf(item);
The following example shows one way to use path manipulation and
get
to retrieve an array item and its index from inside an observer:
// Log user name changes by index
usersChanged(cr) {
// handle paths like 'users.#4.name'
var nameSubpath = cr.path.indexOf('.name');
if (nameSubpath) {
var itempath = cr.path.substring(0, nameSubpath);
var item = this.get(itempath);
var index = cr.base.indexOf(item);
console.log('Item ' + index + ' changed, new name is: ' + item.name);
}
}
Array mutation methods
When modifying arrays, a set of array mutation methods are provided on Polymer
element prototypes which mimic Array.prototype
methods, with the exception that
they take a path
string as the first argument. The path
argument identifies
an array on the element to mutate, with the following arguments matching those
of the native Array
methods.
These methods perform the mutation action on the array, and then notify other elements that may be bound to the same array of the changes. You must use these methods when mutating an array to ensure that any elements watching the array (via observers, computed properties, or data bindings) are kept in sync.
Every Polymer element has the following array mutation methods available:
push(path, item1, [..., itemN])
pop(path)
unshift(path, item1, [..., itemN])
shift(path)
splice(path, index, removeCount, [item1, ..., itemN])
Example:
<dom-module id="custom-element">
<template>
<template is="dom-repeat"></template>
</template>
<script>
Polymer({
is: 'custom-element',
addUser: function(user) {
this.push('users', user);
},
removeUser: function(user) {
var index = this.users.indexOf(user);
this.splice('users', index, 1);
}
});
</script>
</dom-module>
Using native array mutation methods
Whenever possible you should always use Polymer’s
array mutation methods. However, this isn’t always
possible. For example, you may be using a third-party library
that does not use Polymer’s array mutation methods.
In these scenarios you can call
notifySplices
after each mutation to ensure that any Polymer elements observing the array
are properly notified of the changes.
Always include dependencies as observer arguments
Observers shouldn’t rely on any properties, sub-properties, or paths other than those listed as arguments to the observer. This is because the observer can be called while the other dependencies are still undefined. For example:
properties: {
firstName: {
type: String,
observer: 'nameChanged'
},
lastName: {
type: String
}
},
// WARNING: ANTI-PATTERN! DO NOT USE
nameChanged: function(newFirstName, oldFirstName) {
// this.lastName could be undefined!
console.log('new name:', newFirstName, this.lastName);
}
Note that Polymer doesn’t guarantee that properties are initialized in any particular order.
In general, if your observer relies on multiple dependencies, use a multi-property observer and list every dependency as an argument to the observer. This ensures that all dependencies are defined before the observer is called.
properties: {
firstName: {
type: String
},
lastName: {
type: String
}
},
observers: [
'nameChanged(firstName, lastName)'
],
nameChanged: function(firstName, lastName) {
console.log('new name:', firstName, lastName);
}
If you must use a single property and must rely on other properties (for example, if you need access to the old value of the observed property, which you won’t be able to access with a multi-property observer), take the following precautions:
- Check that all dependecies are defined
(for example,
if this.lastName !== undefined
) before using them in your observer. - Set default values on the dependencies.
Keep in mind, however, that the observer is only called when one of the
dependencies listed in its arguments changes. For example, if an observer
relies on this.firstName
but does not list it as an argument, the observer
is not called when this.firstName
changes.
Property change notification events (notify)
When a property is set to notify: true
, an event is fired whenever the
property value changes. The event name is:
property-name-changed
Where property-name
is the dash-case version of
the property name. For example, a change to this.firstName
fires
first-name-changed
.
These events are used by the two-way data binding system. External
scripts can also listen for events (such as first-name-changed
)
directly using addEventListener
.
For more on property change notifications and data binding, see Property change notification and two-way binding.
Read-only properties
When a property only “produces” data and never consumes data, this can be made
explicit to avoid accidental changes from the host by setting the readOnly
flag to true
in the properties
property definition. In order for the
element to actually change the value of the property, it must use a private
generated setter of the convention _setProperty(value)
.
<script>
Polymer({
properties: {
response: {
type: Object,
readOnly: true,
notify: true
}
},
responseHandler: function(response) {
this._setResponse(response);
}
});
</script>
For more on read-only properties and data binding, see Property change notification and two-way binding.
Computed properties
Polymer supports virtual properties whose values are calculated from other properties.
To define a computed property, add it to the properties
object with a
computed
key mapping to a computing function:
fullName: {
type: String,
computed: 'computeFullName(first, last)'
}
The function is provided as a string with dependent properties as arguments in parenthesis. The function will be called once for any change to the dependent properties.
The computing function is not invoked until all dependent properties
are defined (!== undefined
). So each dependent properties should have a
default value
defined in properties
(or otherwise be initialized to a
non-undefined
value) to ensure the property is computed.
Note: The definition of a computing function looks like the definition of a multi-property observer, and the two act almost identically. The only difference is that the computed property function returns a value that’s exposed as a virtual property.
<dom-module id="x-custom">
<template>
My name is <span>{{fullName}}</span>
</template>
<script>
Polymer({
is: 'x-custom',
properties: {
first: String,
last: String,
fullName: {
type: String,
// when `first` or `last` changes `computeFullName` is called once
// and the value it returns is stored as `fullName`
computed: 'computeFullName(first, last)'
}
},
computeFullName: function(first, last) {
return first + ' ' + last;
}
});
</script>
</dom-module>
Arguments to computing functions may be simple properties on the element, as
well as any of the arguments types supported by observers
, including paths,
paths with wildcards, and paths to array splices.
The arguments received by the computing function match those described in the sections referenced above.
Note: If you only need a computed property for a data binding, you can use a computed binding instead. See Computed bindings.
Reflecting properties to attributes
In specific cases, it may be useful to keep an HTML attribute value in sync with
a property value. This may be achieved by setting reflectToAttribute: true
on
a property in the properties
configuration object. This will cause any change
to the property to be serialized out to an attribute of the same name.
<script>
Polymer({
properties: {
response: {
type: Object,
reflectToAttribute: true
}
},
responseHandler: function(response) {
this.response = 'loaded';
// results in this.setAttribute('response', 'loaded');
}
});
</script>
Attribute serialization
When reflecting a property to an attribute or binding a property to an attribute, the property value is serialized to the attribute.
By default, values are serialized according to value’s current type
(regardless of the property’s type
value):
String
. No serialization required.Date
orNumber
. Serialized usingtoString
.Boolean
. Results in a non-valued attribute to be either set (true
) or removed (false
).Array
orObject
. Serialized usingJSON.stringify
.
To supply custom serialization for a custom element, override your element’s serialize
method.