# Data system concepts 1. Observers- Callbacks invoked when data changes. 2. Computed properties- Virtual properties computed based on other properties, and recomputed when the input data changes. 3. Data bindings- Annotations that update the properties, attributes, or text content of a DOM node when data changes. Simple element: class NameCard extends PolymerElement { constructor() { super(); this.name = {first: 'Kai', last: 'Li'}; } static get template() { return html `
{{item}}
`; } constructor() { super(); this._reset(); } addUser() { var user = "wing sang"; this.push('users', user); } removeUser() { var user = "wing sang"; var index = this.users.indexOf(user); this.splice('users', index, 1); } _reset() { this.users=["wing sang", "deshaun", "kelley"]; } } customElements.define('x-custom', XCustom); ### Batch multiple property changes Use **setProperties** method to make a batch change to a set of properties. This ensures the property changes run as a coherent set. this.setProperties({ date: 'Jan 17, 2017', verified: true }); **setProperties** supports an optional **setReadOnly** flag as the second parameter. If you need to set read-only properties as part of a batch change, pass true for the second parameter: this.setProperties({ date: 'Jan 17, 2017', verified: true }, true); #### Link two paths to the same object linkPaths('selectedUser', 'users.1'); To remove a path linkage, call **unlinkPaths**, passing in the first path you passed to linkPaths: unlinkPaths('selectedUser'); # Observers and computed properties - Simple observers observe a single property. - Complex observers can observe one or more properties or paths. ## Simple observers Simple observers are declared in the properties object, and always observe a single property. Simple observers only fire when the property itself changes. They don't fire on subproperty changes, or array mutation. If you need these changes, use a complex observer with a wildcard path ### Observe a property Define a simple observer by adding an observer key to the property's declaration, identifying the observer method by name. Example import { PolymerElement } from '@polymer/polymer/polymer-element.js'; class XCustom extends PolymerElement { static get properties() { return { active: { type: Boolean, // Observer method identified by name observer: '_activeChanged' } } } // Observer method defined as a class method _activeChanged(newValue, oldValue) { this.toggleClass('highlight', newValue); } } customElements.define('x-custom', XCustom); The observer method is usually defined on the class itself, although an observer method can also be defined by a superclass, subclass, or a class mixin, as long as the named method exists on the element. ## Complex observers Complex observers are declared in the observers array. Complex observers can monitor one or more paths. These paths are called the observer's dependencies. static get observers() { return [ // Observer method name, followed by a list of dependencies, in parenthesis 'userListChanged(users.*, filter)' ] } Each dependency represents: - A specific property (for example, firstName). - A specific subproperty (for example, address.street). - Mutations on a specific array (for example, users.splices). - All subproperty changes and array mutations below a given path (for example, users.*). ### Observe changes to multiple properties To observe changes to a set of properties, use the observers array. Observers do not receive old values as arguments, only new values. Only single-property observers defined in the properties object receive both old and new values. Example import { PolymerElement } from '@polymer/polymer/polymer-element.js'; class XCustom extends PolymerElement { static get properties() { return { preload: Boolean, src: String, size: String } } // Each item of observers array is a method name followed by // a comma-separated list of one or more dependencies. static get observers() { return [ 'updateImage(preload, src, size)' ] } // Each method referenced in observers must be defined in // element prototype. The arguments to the method are new value // of each dependency, and may be undefined. updateImage(preload, src, size) { // ... do work using dependent values } } customElements.define('x-custom', XCustom); ## Observe sub-property changes - 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, or onNameChange(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. Example import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; class XCustom extends PolymerElement { static get template () { return html` `; } static get properties() { return { user: { type: Object, value: function() { return {}; } } } } // Observe the name sub-property on the user object static get observers() { return [ 'userNameChanged(user.name)' ] } // For a property or sub-property dependency, the corresponding // argument is the new value of the property or sub-property userNameChanged: function(name) { if (name) { console.log('new name: ' + name); } else { console.log('user name is undefined'); } } } customElements.define('x-custom', XCustom); ## Observe array mutations static get observers() { return [ 'usersAddedOrRemoved(users.splices)' ] } - indexSplices. The set of changes that occurred to the array, in terms of array indexes. Each indexSplices record contains the following properties: - index. Position where the splice started. - removed. Array of removed items. - addedCount. Number of new items inserted at index. - object: A reference to the array in question. - type: The string literal 'splice'. Example import { PolymerElement } from '@polymer/polymer/polymer-element.js'; class XCustom extends PolymerElement { static get properties() { return { users: { type: Array, value: function() { return []; } } } } // Observe changes to the users array static get observers() { return [ 'usersAddedOrRemoved(users.splices)' ]; } // For an array mutation dependency, the corresponding argument is a change record usersAddedOrRemoved(changeRecord) { if (changeRecord) { changeRecord.indexSplices.forEach(function(s) { s.removed.forEach(function(user) { console.log(user.name + ' was removed'); }); for (var i=0; iMy name is {{fullName}}
`; } static get properties() { return { 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(first, last) { return first + ' ' + last; } } customElements.define('x-custom', XCustom); ## Dynamic observer methods For example, import { PolymerElement } from '@polymer/polymer/polymer-element.js'; class NameCard extends PolymerElement { static get properties() { return { // Override default format by assigning a new formatter // function formatter: { type: Function }, formattedName: { computed: 'formatter(name.title, name.first, name.last)' }, name: { type: Object, value() { return { title: "", first: "", last: "" }; } } } } constructor() { super(); this.formatter = this.defaultFormatter; } defaultFormatter(title, first, last) { return `${title} ${first} ${last}` } } customElements.define('name-card', NameCard); Setting a new value for formatter causes the formattedName property to update, even if the name property doesn't change: nameCard.name = { title: 'Admiral', first: 'Grace', last: 'Hopper'} console.log(nameCard.formattedName); // Admiral Grace Hopper nameCard.formatter = function(title, first, last) { return `${last}, ${first}` } console.log(nameCard.formattedName); // Hopper, Grace ### Add a simple observer dynamically You can create a simple observer dynamically using the _createPropertyObserver instance method. For example: this._observedPropertyChanged = (newVal) => { console.log('observedProperty changed to ' + newVal); }; this._createPropertyObserver('observedProperty', '_observedPropertyChanged', true); ### Add a complex observer dynamically You can create a computed property dynamically using the _createMethodObserver instance method. For example: this._createMethodObserver('_observeSeveralProperties(prop1,prop2,prop3)', true); ### Add a computed property dynamically You can create a computed property dynamically using the _createComputedProperty instance method. For example: this._createComputedProperty('newProperty', '_computeNewProperty(prop1,prop2)', true); # Data Bindings A data binding connects data from a custom element (the host element) to a property or attribute of an element in its local DOM (the child or target element). static get template() { return html`