Home Start Get started Quick tour of Polymer Install the Release Candidate Polymer Feature overview What's new About Polymer 2.0 Upgrade guide Hybrid elements Release notes Custom elements Custom element concepts Define an element Declare properties Shadow DOM & styling Shadow DOM concepts DOM templating Style shadow DOM Custom CSS properties Events Handle and fire events Gesture events Data system Data system concepts Work with object and array data Observers and computed properties Data binding Helper elements Tools Tools overview Polymer CLI Document your elements Test your elements Optimize for production Advanced tools Services polymer.json specification Node support Resources Browser compatibility Glossary API Reference API Reference App Toolbox What's in the box? Using the Toolbox App templates Responsive app layout Routing Localization App storage Service worker Serve your app Case study Shop News Blog Community Feature overview
What's new
About Polymer 2.0 Upgrade guide Hybrid elements Release notes
Custom elements
Custom element concepts Define an element Declare properties
Shadow DOM & styling
Shadow DOM concepts DOM templating Style shadow DOM Custom CSS properties
Events
Handle and fire events Gesture events
Data system
Data system concepts Work with object and array data Observers and computed properties Data binding Helper elements
Tools
Tools overview Polymer CLI Document your elements Test your elements Optimize for production Advanced tools Services polymer.json specification Node support
Resources
Browser compatibility Glossary
API Reference
API Reference

Observers are methods invoked when observable changes occur to the element's data. There are two basic types of observers:

  • Simple observers observe a single property.
  • Complex observers can observe one or more properties or paths.

You use different syntax for declaring these two types of observers, but in most cases they function the same way.

Computed properties are virtual properties based on one or more pieces of the element's data. A computed property is generated by a computing function—essentially, a complex observer that returns a value.

Unless otherwise specified, notes about observers apply to simple observers, complex observers, and computed properties.

The first call to an observer is deferred until the following criteria are met:

  • The element is fully configured (default values have been assigned and data bindings propagated).
  • At least one of the dependencies is defined (that is, they don't have the value undefined).

After the inital call, each observable change to a dependency generates a call to the observer, even if the new value for the dependency is undefined.

This allows the element to avoid running observers in the default case.

Like all property effects, observers are synchronous. If the observer is likely to be invoked frequently, consider deferring time-consuming work, like inserting or removing DOM. For example, you can use the Polymer.Async module to defer work, or use the Polymer.Debouncer module to ensure that a task is only run once during a given time period.

However, if you handle a data change asynchronously, note that the change data may be out of date by the time you handle it.

Simple observers are declared in the properties object, and always observe a single property. You shouldn't assume any particular initialization order for properties: if your observer depends on multiple properties being initialized, use a complex observer instead.

Simple observers are fired the first time the property becomes defined (!= undefined), and on every change thereafter, even if the property becomes undefined.

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, as described in Observe all changes related to a path.

You specify an observer method by name. The host element must have a method with that name.

The observer method receives the new and old values of the property as arguments.

Define a simple observer by adding an observer key to the property's declaration, identifying the observer method by name. Example:

class XCustom extends Polymer.Element {

  static get is() {return 'x-custom'; }

  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);
  }
}

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.

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.

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.*).

The observer method is called with one argument for each dependency. The argument type varies depending on the path being observed.

  • For simple property or subproperty dependencies, the argument is the new value of the property or subproperty.

  • For array mutations the argument is a change record describing the change.

  • For wildcard paths, the argument is a change record describing the change, including the exact path that changed.

Note that any of the arguments can be undefined when the observer is called.

Complex observers should only depend on their declared dependencies.

Related task:

  • Observe multiple properties or paths
  • Observe array changes
  • Observe all changes to a path

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 invoked once at Multi-property observers and computed properties run once at initialization if any dependencies are defined. So each dependent properties should have a default value defined in properties (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 the properties object receive both old and new values.

Example

class XCustom extends Polymer.Element {

  static get is() {return 'x-custom'; }

  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
  }
}

In addition to properties, observers can also observe paths to sub-properties, paths with wildcards, or array 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, 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.

In order for Polymer to properly detect the sub-property change, the sub-property must be updated in one of the following two ways:

Example:

<dom-module id="x-custom">
  <template>
    <!-- Sub-property is updated via property binding. -->
    <input value="{{user.name::input}}">
  </template>
  <script>
    class XCustom extends Polymer.Element {

      static get is() {return 'x-custom'; }

      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(XCustom.is, XCustom);
  </script>
</dom-module>

Use an array mutation observer to call an observer function whenever an array item is added or deleted using Polymer's array mutation methods. Whenever the array is mutated, the observer receives a change record representing the mutation as a set of array splices.

Polymer only calls the array mutation observer when the array items change, not for changes to the top-level array

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 wildcard path, as described in Observe all changes related to a path.

Observable array mutation. Use Polymer's array mutation methods wherever possible to ensure that elements with registered interest in the array mutations are properly notified. If you can't avoid the native methods, you need to notify Polymer about array changes as described in Using native array mutation methods.

To create a splice observer, specify a path to an array followed by .splices in your observers array.

static get observers() {
  return [
    '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. 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'.

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

class XCustom extends Polymer.Element {

  static get is() {return 'x-custom'; }

  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; 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() {
    super.ready();
    this.push('users', {name: "Jack Aubrey"});
  }
}

customElements.define(XCustom.is, XCustom);

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 value field includes the indexSplices property described in Observe array mutations.

Example:

<dom-module id="x-custom">
  <template>
    <input value="{{user.name.first::input}}"
           placeholder="First Name">
    <input value="{{user.name.last::input}}"
           placeholder="Last Name">
  </template>
  <script>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      static get properties() {
        return {
          user: {
            type: Object,
            value: function() {
              return {'name':{}};
            }
          }
        }
      }

      static get observers() {
        return [
            'userNameChanged(user.name.*)'
        ]
      }

      userNameChanged(changeRecord) {
        console.log('path: ' + changeRecord.path);
        console.log('value: ' + changeRecord.value);
      }
    }

    customElements.define(XCustom.is, XCustom);
  </script>
</dom-module>

Observers shouldn't rely on any properties, sub-properties, or paths other than those listed as dependencies of the observer. This creates "hidden" dependencies, which can result in unexpected behavior:

  • The observer can be called before the hidden dependency is configured.
  • The observer isn't called when the hidden dependency changes.

For example:

static get properties() {
  return {
    firstName: {
      type: String,
      observer: 'nameChanged'
    },
    lastName: {
      type: String
    }
  }
}

// WARNING: ANTI-PATTERN! DO NOT USE
nameChanged(newFirstName, oldFirstName) {
  // Not invoked when this.lastName changes
  var fullName = 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 configured before the observer is called.

static get properties() {
  return {
    firstName: {
      type: String
    },
    lastName: {
      type: String
    }
  }
}

static get observers() {
  return [
    'nameChanged(firstName, lastName)'
  ]
}

nameChanged: function(firstName, lastName) {
  console.log('new name:', firstName, lastName);
}

If you must use a single property observer 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.

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 a dependency, the observer is not called when this.firstName changes.

Computed properties are virtual properties computed on the basis of one or more paths. The computing function for a computed property follows the same rules as a complex observer, except that it returns a value, which is used as the value of the computed property.

As with complex observers, the handling of undefined values depends on the number of properties being observed. See the description of Complex observers for details.

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 observable change to the dependent properties.

As with complex observers, the handling of undefined values depends on the number of properties being observed.

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>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      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(XCustom.is, XCustom);
  </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.