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:
1 2 3 4 5 6 7 8 9 10 | 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:
1 2 3 4 5 6 | // observe the count property for changes ... _countChanged(newVal, oldVal) { // handle changes ... } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 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:
1 2 3 4 5 6 | // observe both username and password properties for changes ... _credentialsChanged(username, password) { // handle changes ... } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // 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 ... } |
Deep Sub-Property Observers
As with explicit Sub-Property Observers, (n-level) arbitrary sub-properties can be observed using wildcard notation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 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 ... } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 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 ... } } |
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.