Computed Properties in Ember.Js

computed properties

Note: Short version of this post is a part of Ember.Js Guides.

What Are Computed Properties?

In a nutshell, it’s a property whose value is computed the first time it’s asked for. You can define the computed property as a function and when someone asks for it, Ember will automatically invoke the function and treat the return value like value of the property.

Here’s a very well-known example:

App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,
  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName')
});

var ironMan = Person.create({
  firstName: "Tony",
  lastName:  "Stark"
});

ironMan.get('fullName');
// "Tony Stark"

The code above defines a computed property fullName by calling property() on the function with two dependencies firstName and lastName and whenever it gets called, it returns firstName + lastName.

Inception

Let’s take a look at another example. Say we want to add a description computed property to App.Person. It will aggregate other properties like fullName, age, country:

App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,
  age: null,
  country: null,
  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName'),
  description: function() {
    return this.get('fullName') + '; Age: ' +
           this.get('age') + '; Country: ' +
           this.get('country');
  }.property('fullName', 'age', 'country')
});

var captainAmerica = Person.create({
  fullName: 'Steve Rogers',
  age: 80,
  country: 'USA'
});

captainAmerica.get('description');
// "Steve Rogers; Age: 80; Country: USA"

Notice that you can use an existing computed property as a dependency for a new one.

Caching

By default, all computed properties are cached. That means that once you requested the value of computed property (called get on it), it’s going to compute and cache its value:

captainAmerica.get('description');
// computes the value and returns "Steve Rogers; Age: 80; Country: USA"
captainAmerica.get('description');
// returns cached "Steve Rogers; Age: 80; Country: USA"

A computed property gets recomputed when any of the properties it depends on change:

captainAmerica.set('country', 'United States of America');
captainAmerica.get('description'); // computes the value and returns"Steve Rogers; Age: 80; Country: United States of America"

Read Only

This property is false by default. You won’t be able to set the value of the computed property if you call readOnly on it:

App.Person = Ember.Object.extend({
  description: function() {
    // implementation
  }.property('fullName', 'age', 'country').readOnly()
});

var captainAmerica = Person.create();
captainAmerica.set('description', 'hero');
// "Cannot Set: description on: <(unknown mixin):ember133>"

Alternative syntax for defining Computed Properties

This code:

App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,
  fullName: Ember.computed('firstName', 'lastName', function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  })
});

does exactly the same thing as this code:

App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,
  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName')
});

with the difference that the first example works if you disable Ember’s prototype extension.

How are Computed Properties different from Observers and Bindings?

The concept of observer is pretty simple. You have something that you want to track the change of. You add an observer to it, so next time it changes, a certain event is going to be fired notifying you that that something has changed.

There are two types of observers: before (observesBefore) and after (observes). When observer event (callback) is fired, it’s called with two arguments: obj and keyName. It doesn’t pass the value of the property to the event (callback). The reason is because the property you’re watching might be lazily computed.

Observers are used by CP internally to invalidate CP’s cache when its dependency keys were changed. Observers (like CPs) don’t use runloop magic (fired “right away”).

Observers are not going to fire if the value is unchanged from before (changing existing lastName from Stark to Stark won’t trigger the observer callback).

Bindings is an internal concept that is not meant to be used. I’m not saying you can’t, it’s better not to. Typically, you don’t need to use it in your application, using CP is plenty enough.

Bindings are meant to keep a property of two objects in sync. Their update (sync) happens through run loop, so there might be a period of time when two objects have the same property with different values and only by the end of a sync queue those values are going to be the same.

For example, in Ember those two objects are controller and view (any time a controller’s property changes, view’s property changes as well).

What do I use and when?

Computed properties are good for combining other properties or doing transformations on the property.

Observers are good for tracking changes of a property and reacting to them. Observers should contain behaviour that reacts to the change.

Bindings are used to make sure that the properties from the different objects are in sync. They are rarely used and most of the times can be replaced with computed properties.

Futher reading

You can read more about Computed Properties and Ember’s Object Model over here. Happy Coding!

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box