-
Notifications
You must be signed in to change notification settings - Fork 33
Properties
You can declare properties on your custom element by adding the @property
annotation to any public field of the class. This 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 annotated with
@property.
In addition, the Property
annotation can be used to specify:
- Property change observer. Calls a method whenever the property value changes.
- 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:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created():
@Property(observer: 'userChanged')
String user;
@Property(reflectToAttribute: true)
bool isHappy;
@Property(notify: true)
num get count;
@Property(computed: 'computeUserMessage(user, isHappy)')
String userMessage;
@reflectable
void userChanged(String newUser, String oldUser) { ... }
@reflectable
String computeUserMessage(String user, bool isHappy) =>
'$user is{!isHappy ? ' not' : ''} happy';
}
The Property
object supports the following named arguments for each property:
Key | Details |
---|---|
reflectToAttribute | Type: bool Set to |
notify | Type: bool If |
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 should never be written to directly. 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 |
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 0.8** — attribute to property mappings are set up on the element at registration time based on the rules described above.
If a field is annotated with @property, an attribute on the instance matching the property name will be deserialized according to the type specified and assigned to a field of the same name on the element instance.
The type system includes support for Map and List values expressed as JSON, or Date objects expressed as any Date-parsable string representation. Boolean properties set based on the existence of the attribute: if the attribute exists at all, its value is true, regardless of its string-value (and the value is only false if the attribute does not exist).
Example:
x_custom.dart:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@property
String user;
@Property(notify: true)
bool manager = false;
attached() {
text = 'Hello World, my user is ${user == null ? 'nobody' : user}.\n'
'This user is ${manager ? '' : 'not'} a manager.';
}
}
index.html:
<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:
x_custom.dart:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@property
String userName;
}
index.dart:
<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.
Custom element properties may be observed for changes by specifying observer
argument of the Property
annotation for the field with 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:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@Property(observer: 'disabledChanged')
bool disabled;
@Property(observer: 'highlightChanged')
bool highlight;
@reflectable
void disabledChanged(newValue, oldValue) {
toggleClass('disabled', newValue);
set('highlight', true);
}
@reflectable
void highlightChanged([_, __]) {
classes.add('highlight');
async(() {
classes.remove('highlight');
}, waitTime: 300);
}
}
**Dart note:** You must also annotate each of the methods with @reflectable in order to make them available via reflection.
Compatibility note: The argument order for change handlers is currently the opposite of the order used in 0.5.
Dart note: Property change observation is currently achieved in Polymer Dart
by using the set
function to modify any properties on your class. This is
different from Polymer JS because we cannot dynamically create fields on your
class. We will investigate alternatives for Polymer Dart 1.1.0.
To observe changes to a set of properties, use the @Observe(...)
annotation.
These observers differ from single-property observers in a few ways:
- Observers are not invoked until all dependent properties are defined (
!= null
).
So each dependent properties should have a default value assigned (or otherwise be initialized to a non-null
value) to ensure the observer is called. - Observers do not receive
old
values as arguments, only new values. Only single-property observers defined in theProperty
object receive bothold
andnew
values.
Example:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@property
bool preload;
@property
String src;
@property
String size;
@Observe('preload, src, size')
void updateImage(bool newPreload, String newSrc, String newSize) {
// ... do work using dependent values
}
}
In addition to properties, observers can also observe paths to sub-properties, paths with wildcards, or array changes.
You can also observe changes to object sub-properties using the
Observe
annotation, by specifying a full path (user.manager.name
)
as a function argument.
Example:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@property
User user;
@Observe('user.manager')
void userManagerChanged(User newUser) {
print('new manager name is ' + newUser.name);
}
}
To observe a change to a path (object sub-property) the value must be changed in one of the following ways:
- Using a Polymer property binding to another element.
- Using the
set
API, which provides the required notification to elements with registered interest.
**Dart note:** The above rules also apply to top-level properties in Dart.
To call an observer when any (deep) sub-property of an
object 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. -
value
. New value of the path that changed. -
base
. The object matching the non-wildcard portion of the path.
Example:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@property
User user;
@Observe('user.manager.*')
void userManagerChanged(Map changeRecord) {
if (changeRecord['path'] == 'user.manager') {
// user.manager object itself changed
print('new manager name is ' + changeRecord['value'].name);
} else {
// sub-property of user.manager changed
print('${changeRecord['path']} changed to ${changeRecord['value']}');
}
}
}
Finally, to observe mutations to lists (changes resulting from calls to add
,
addAll
, clear
, fillRange
, insert
, insertAll
, removeItem
, removeAt
,
removeLast
, removeRange
, removeWhere
, replaceRange
, retainWhere
,
setAll
, and setRange
), specify a path to an array followed by .splices
as
an argument to the observer function.
The value received by the observer for the splices
path of a list is a
change records with the following properties:
-
indexSplices
. Lists the set of changes that occurred to the list, in terms of list indicies. EachindexSplices
record contains the following properties:-
index
. Position where the splice started. -
removed
. List ofremoved
items. -
addedCount
. Number of new items inserted atindex
.
-
-
keySplices
. Lists the set of changes that occurred to the array in terms of "keys" used by Polymer for identifying list elements. EachkeySplices
record contains the following properties:-
added
. Array of added keys. -
removed
. Array of removed keys.
-
Example:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@property
List<User> users = [];
@Observe('users.splices')
void usersAddedOrRemoved(Map changeRecord) {
if (changeRecord == null) return;
changeRecord['indexSplices'].forEach((s) {
s['removed'].forEach((user) {
print('${user.name} was removed');
});
print('${s['addedCount']} users were added');
});
}
void addUser() {
add('users', new User("Jack Aubrey"));
}
}
List mutation methods. Observing changes to arrays is dependent on the change to the list being made through one of the list mutation methods provided on Polymer elements, which provides the required notification to elements with registered interest.
When you specify a wildcard path on an array, the observer is for both splices as well as array element sub-property changes. So the observer in the following example will be called for all additions, removals, and deep changes that occur in the array:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@property
List<User> users = [];
@Observe('users.*')
void usersChanged(Map changeRecord) {
if (changeRecord['path'] == 'users.splices') {
// a user was added or removed
} else {
// an individual user or its sub-properties changed
// check "changeRecord.path" to determine what changed
}
}
}
When modifying lists, a set of list mutation methods are provided by the
PolymerMixin
class which mimic the List
api, with the exception that
they take a path
string as the first argument and a few other minor
differences. The path
argument identifies a list on the element to mutate,
with the following arguments matching those of the native List
methods.
These methods perform the mutation action on the list, and then notify other elements that may be bound to the same list of the changes. You must use these methods when mutating a list to ensure that any elements watching the list (via observers, computed properties, or data bindings) are kept in sync.
Every Polymer element has the following list mutation methods available:
add(String path, item)
addAll(String path, Iterable items)
clear(String path)
fillRange(String path, int start, int end, [fillValue])
insert(String path, int index, element)
insertAll(String path, int index, Iterable elements)
-
removeItem(String path, value)
-
remove
was renamed toremoveItem
because HtmlElement already has a method by that name.
-
removeAt(String path, int index)
removeLast(String path)
removeRange(String path, int start, int end)
removeWhere(String path, bool test(element))
replaceRange(String path, int start, int end, Iterable replacement)
retainWhere(String path, bool test(element))
setAll(String path, int index, Iterable iterable)
setRange(String path, int start, int end, Iterable iterable, [int skipCount = 0])
Example:
custom_element.html:
<dom-module id="custom-element">
<template>
<template is="dom-repeat">{{users}}</template>
</template>
</dom-module>
custom_element.dart:
@PolymerRegister('custom-element')
class CustomElement extends PolymerElement {
CustomElement.created() : super.created();
void addUser(User user) {
add('users', user);
}
void removeUser(User user) {
removeItem('users', user);
}
}
When a property is set to notify: true
, an event,
propertyName-changed
, is fired whenever the property
value changes. These events are used by the two-way data binding system, and can
also notify external scripts and frameworks to respond to changes in the element.
For more on property change notifications and data binding, see Property change notification and two-way binding.
When a property only "produces" data and never consumes data, this can be made
explicit to avoid accidental changes from the host by defining only a getter
for the field. In order for the element to actually change the value of the
property, it must use a private variable which holds the actual value,
following normal dart semantics. Then, to notify the system that the value has
changed, you must call notifyPath('propertyName', value);
.
@PolymerRegister('my-element')
class MyElement extends PolymerElement {
MyElement.created() : super.created();
@property
String get myValue => _myValue;
String _myValue;
@reflectable
someEventHandler() {
_myValue = 'hello!';
notifyPath('myValue', _myValue);
}
}
**Dart note:** In Polymer Js they create magic `_set{{PropertyName}}` functions, which set the value and call notifyPath. In dart we can't create these on the fly, so you have to manually call `notifyPath`.
For more on read-only properties and data binding, see Property change notification and two-way binding.
Polymer supports virtual properties whose values are calculated from other properties.
To define a computed property, pass computed: 'someFunction(...)
to the
@Property
annotation:
@Property(computed: 'computeFullName(first, last)');
String fullName;
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 (!= null
). So each dependent properties should have a
default value (or otherwise be initialized to a non-null
value) to ensure the
property is computed.
**Note:** The definition of a computing function looks like the definition of a [multi-property observer](#observing-changes-to-multiple-properties), 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.
x_custom.html:
<dom-module id="x-custom">
<template>
My name is <span>{%raw%}{{fullName}}{%endraw%}</span>
</template>
</dom-module>
x_custom.dart:
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@property
String first;
@property
String last;
@Property(computed: 'computeFullName(first, last)')
String fullName;
@reflectable
String computeFullName(String first, String last) {
return '$first $last';
}
}
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](data-binding-syntax#computed-bindings).
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 Property
annotation object. This will cause any change
to the property to be serialized out to an attribute of the same name.
@PolymerRegister('x-custom')
class XCustom extends PolymerElement {
XCustom.created() : super.created();
@Property(reflectToAttribute: true)
String myString;
ready() {
set('myString', 'ready');
// results in attributes['my-string'] = 'ready';
}
}
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.
-
String
. No serialization required. -
DateTime
ornum
. Serialized usingtoString
. -
bool
. Results in a non-valued attribute to be either set (true
) or removed (false
). -
List
orMap
. Serialized usingJSON.stringify
.
To supply custom serialization for a custom element, override your element's serialize
method.