Computed Properties
By design, Handlebars templates don’t allow complex expressions. You are given
an {{#if}}
block helper, but it can only evaluate whether something is “truthy” (aka true
, a non-empty
string or array or other value that is not undefined
or null
.)
For example, you can’t do something like this:
Handlebars encourages you to use a single evaluation for your logic:
I think the second example is much clearer and easier to follow. Even though some other templating languages might allow you to use more complex expressions in your templates like the first example, I’d recommend aggregating them anyway. It will make your life a lot easier.
Ember gives you a great tool out of the box to do this easily called computed properties. I’ve written about them before, but in a nutshell they allow you to create a function that transforms one or more properties into another single property. In this case, you could make one like this:
var Creature = Ember.Object.extend({
purplePeopleEater: function() {
return (this.get('eyes.length') === 1) &&
(this.get('horns.length') === 1) &&
(this.get('flies') &&
(this.get('color') === 'purple') &&
(this.get('eatsDudes')));
}.property('eyes.length', 'horns.length', 'flies', 'color', 'eatsDudes')
});
By adding .property()
to your function prototype, this tells Ember that it’s a computed property. The
5 arguments tell Ember to cache the result unless any of those properties change. As long as our
Creature
’s values don’t change, the result won’t be recalculated.
Computed Property Macros
Computed Properties are an important feature in Ember and are understood well developers. However,
there is a less well known way to declare computed properties that is often a lot easier and clearer: using
the Ember.computed
set of functions. They are shortcuts for creating common types of computed properties. Here’s
some examples:
var Creature = Ember.Object.extend({
eyesBlue: Ember.computed.equal('eyes', 'blue'),
hasTwoBananas: Ember.computed.gte('bananas.length', 2),
sick: Ember.computed.not('healthy'),
radical: Ember.computed.and('ninja', 'turtle'),
staff: Ember.computed.or('moderator', 'admin')
});
You can find a full list of macros on the Ember API site.
Not only are the computed property macros often shorter to write, but you don’t have to specify the dependant
properties for caching like you do when using the .property()
method, so I find them to be less error
prone. Here’s how I might re-write the purplePeopleEater
property by breaking it into smaller
macros:
var Creature = Ember.Object.extend({
oneEyed: Ember.computed.equal('eyes.length', 1),
oneHorned: Ember.computed.equal('horns.length', 1),
purple: Ember.computed.equal('color', 'purple'),
purplePeopleEater: Ember.computed.and('oneEyed', 'oneHorned', 'flies', 'purple', 'eatsDudes')
});
It’s a lot more DRY than the previous solution. In the first solution we had to write every property name twice, now we only write it once.
A secondary benefit of this approach is we’ve cached the internal properties such as oneEyed
. If another
computed property needed that comparison, we already have it available and cached.
Once you start to use these macros you won’t stop! You’ll find yourself breaking long sequences of conditionals into smaller boolean chunks that are easier to re-use and cache for performance. Have fun!