Property Change Observers in Polymer

When building Web Components the ability to observe property / attribute changes on custom elements and respond to them accordingly can prove quite useful.

Fortunately, Polymer makes this incredibly easy. Let’s take a quick look …
 (note, we’ll be using ES6 here)

Single Property Observers

In it’s most basic form, a Single Property Observer can be defined by simply implementing a method and adding it to the property’s observer configuration:

this.properties = {
// define a property ...
count: {
type: Number,
// specify an observer to be invoked when the property changes ...
observer: '_countChanged'
}
};

Now, whenever the property changes, Polymer will automatically invoke the observer method; handily passing two arguments: the updated value, and the previous value:

// observe the count property for changes ...
_countChanged(newVal, oldVal) {
// handle changes ...
}

Try it

Pretty cool, right? It gets even better!

Multi-Property Observers

In addition to Single Property Observers, multiple properties can be observed for changes using the observers array:

// define properties ...
this.properties = {
username: {
type: String
},
password: {
type: String
}
};
// observe username and password property changes ...
this.observers = [
'_credentialsChanged(username, password)'
];

The observers array is rather self-explanatory: each item is simply a string representation of the method to be invoked with the observed properties specified as arguments:

// observe both username and password properties for changes ...
_credentialsChanged(username, password) {
// handle changes ...
}

Try it.

For more information, see multi-property-observers.

Sub-Property Observers

Similar to Multi-Property Observers, sub-properties can be observed as well (e.g. user.username, or user.account.name, etc.). For instance:

// define properties ...
this.properties = {
user: {
type: Object,
value: f => {
return {
'username': '',
'password': '',
'account': {
'name': ''
}
};
}
}
};
// define observers ...
this.observers = [
'_credentialsChanged(user.username, user.password)',
'_accountChanged(user.account.name)'
];
...
// observe user.username, and user.password for changes ...
_credentialsChanged(username, password) {
// handle changes ...
}
// observe user.account.name for changes ...
_accountChanged(accountName) {
// handle changes ...
}

Try it

Deep Sub-Property Observers

As with explicit Sub-Property Observers, (n-level) arbitrary sub-properties can be observed using wildcard notation:

// define properties ...
this.properties = {
user: {
type: Object,
value: f => {
return {
'username': '',
'password': '',
'account': {
'name': ''
}
};
}
}
};
...
this.observers = [
'_userChanged(user.*)'
];
...
// handle any changes to the user object ...
_userChanged(changeRecord) {
// handle changes ...
}

Try it.

Both Sub-Property Observers and Deep Sub-Property Observers differ from Single-Property Observers in that a changeRecord is passed to the observer method as opposed to the updated value. A changeRecord is simply an object which contains the following properties (as per the Polymer Docs):

  • changeRecord.path: Path to the property that changed.
  • changeRecord.value: New value of the path that changed.
  • changeRecord.base: The object matching the non-wildcard portion of the path.

It’s important to keep in mind that Sub-Property and Deep Sub-Property observations can only be made using either property bindings or the set method.

Array Mutation Observers

Complimentary to Single, Multi, Sub, and Deep Property Observers, Polymer provides Array Mutation Observers which allow for observing Array and Array element properties for changes.

This is where the API requires a little getting used to IMHO, and so I would recommend reading the Docs in detail.

That being said, Array Mutation Observers are quite powerful, for example:

// define properties ...
this.properties = {
items: {
type: Array,
default: f => {return [];}
}
};
...
this.observers = [
'_itemsChanged(items.*)'
];
...
_itemsChanged(changeRecord) {
if (changeRecord.path === 'items.splices') {
// handle added or removed items ...
} else {
// handle property changes to individual items ...
}
}

Try it.

When observing Arrays, in order for bindings to reflect properly, Polymer’s Array Mutation Methods must be used. This is quite simple in that the API is the same as that of the corresponding Native Array methods, with the only difference being the first argument is the path to the array which is to be modified. For example, rather than: this.items.splice(…) one would simply use: this.splice(‘items’, …).

Conclusion

Hopefully this simple introduction to Polymer Observers has demonstrated some of the powerful capabilities they provide. Understanding how each can be implemented will certainly simplify the implementation of your custom elements, therefore leveraging them where needed is almost always a good design decision.

Feel free to explore any of the accompanying examples.


Originally published at Eric Feminella.