Looking Through Binoculars

Two powerful tools in the Ember.js world are computed properties and observers. If you aren’t familiar with these, don’t worry as examples of both will be given.

Computed Properties

First, let’s look at a computed property:

fullName: (function() {
  return this.get('firstName') + ' ' + this.get('lastName');
}).property('firstName', 'lastName')

This example is a very simple property. It just takes a model’s firstName and lastName and combines them. This property will be updated any time the model’s firstName or lastName changes. This is signified by the collection specified in the property method (the last line of the sample code). We could then take this property and use it in a template.

Observers

An observer is very similar to a computed property, however, it acts more like a method than a property. You wouldn’t use an observer inside a template. Consider the scenario where you have a “native” fullName property (not computed like the previous example). You could update the property using an observer any time the first or last names change. Here’s an example:

updatefullName: (function() {
  var fullName = this.get('firstName') + ' ' + this.get('lastName');
  this.set('fullName', fullName);
}).observes('firstName', 'lastName')

Notice the slight differences? Instead of having a return statement, we simply make a change to our model in the observer. Also instead of property, we use the keyword observes. This is somewhat of a contrived example, as you’d typically just use a computed property like the earlier example, but hopefully you get the idea.

Collections

The previous examples are straightforward and easy to understand, but what if you need to observe something more complicated like a collection? Let’s say you have a model that looks like this (in /app/models/order.js):

export default DS.Model.extend({
  number: DS.attr('number'),
  lineItems: DS.hasMany('line-item', { async: true })
});

And your LineItem model looks like this (in /app/models/line-item.js):

export default DS.Model.extend({
  number: DS.attr('number'),
  price: DS.attr('number'),
  order: DS.belongsTo('order')
});

Now let’s assume that we want to know the total of the order. To get this value, we need to monitor each of the line items for price changes. To watch for these changes, we’ll use the @each property. Here’s what our computed property would look like on our Order model:

total: (function() {
  var lineItems = this.get('lineItems');
  return lineItems.reduce(function(prev, lineItem) {
    return (prev || 0) + lineItem.get('price');
  });
}).property('lineItems.@each.price')

Note there are other ways to accomplish a sum, but for this example hopefully this is easy to understand. The body of the property isn’t that important. What’s really important in this example is the dependencies that we’re passing to the property method. We’re saying to Ember, monitor @each lineItems price. This will update our total property any time a price is modified. You can see why @each is very powerful.

One More Thing

What if we only want to watch for items being added or removed from the collection? Ember has us covered there as well. Instead of @each, we can use the [] property instead. Say we wanted just to update our total only when the collection is altered, here’s what our revised property would look like:

total: (function() {
  ...
}).property('lineItems.[]')

Of course, this is another contrived example, but it should illustrate the point. It’s also important to note that you shouldn’t end a property/observer with @each, so no .property('lineItems.@each'). You must use the [] property instead as shown in the previous example. The usage of ending with an @each was deprecated in Ember 1.13.x and removed in Ember 2.0, so remember to use the correct syntax!