7 Comments

Pure Computed Properties in Ember

Ember.js has a great way of managing state on objects: computed properties. A computed property is basically a cached function call.

If Ember idioms are properly followed, computed properties are pure functions. A pure function, when called with the same arguments, will yield the same result with zero side effects. It’s the fact that they are pure that makes them safe to cache. As long as the inputs do not change, the result does not need to be recomputed, and the cached value can be returned.


  let Person = Ember.Object.extend({
    fullName: Ember.computed('firstName', 'lastNane', function() {
      let firstName = this.get('firstName'),
          lastName  = this.get('lastName');

      return firstName + ' ' + lastName;
    })
  });

As you can see in this example, fullName is pure. It relies on firstName and lastName. The arguments before the function are the dependencies. If any of those change, we’ll need to run our function again if fullName is asked for. But since this function is running in the context of our Person, everything is available. We can introduce side effects by setting properties.

What’s more likely is that the dependencies change and we don’t update both their names and the spot in the function that asks for them. (You did notice the typo in lastNane right?) Bugs like this are incredibly hard to catch, and they can cause subtle problems when a computed property doesn’t always recompute at the right time.

Having been burned by this way too many times, Will and I finally broke down and started enforcing our property purity with pure. pure lets us declare our dependencies and our function to use them, but it does not give us any access to the object itself. Let’s look a quick example:


fullName: computed.pure('firstName', 'lastName', (first, last) => first + ' ' + last),

Since we no longer care about the context, we can use the nice fat-arrow syntax. We only define our dependencies once, and if we get them wrong, our implementation is borked because the desired dependencies are simply not available. We will immediately see that something is wrong, rather than having a subtle bug that will show up later. We can even use this with simple @each dependencies:


names: computed.pure('employees.@each.name', names => names.join(' ')),

While pure does not support all the Ember computed property syntax, it’s a good start. Pure computed properties lower the chance of bugs and clean up the code. I’d like to see pure become officially adopted by Ember. Using guaranteed pure properties should be the default, and the old syntax should only be used/allowed when absolutely necessary.

Feel free to grab and use the helper below and tell us what you like/don’t like about it.


  pure(...args) {
    const f = args.pop();
    return Ember.computed(...args, function() {
      const values = args.map(arg => {
        if (arg.match(/@each/)) {
          const segments = arg.split('.@each.');
          return this.get(segments[0]).map(v => v.get(segments[1]));
        } else if (arg.match(/\[\]/)) {
          const segments = arg.split('.[]');
          return this.get(segments[0]);
        } else {
          return this.get(arg);
        }
      });
      return f(...values);
    });
  }