GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS

Written by thoughtbot

Custom Ember Computed Properties

EmberJS has a lot of features for helping you build a clean JavaScript interface. One of my favorites is the computed property; Ember can watch a property or set of properties and when any of those change, it recalculates a value that is currently displayed on a screen:

fullName: (->
  "#{@get('firstName)} #{@get('lastName')}"
).property('firstName', 'lastName')

Any time the firstName or lastName of the current object change, the fullName property will also be updated.

In my last project I needed to cacluate the sum of a few properties in an array. I started with a computed property:

sumOfCost: (->
  @reduce ((previousValue, element) ->
    currentValue = element.get('cost')
    if isNaN(currentValue)
      previousValue
    else
      previousValue + currentValue
  ), 0
).property('@each.cost')

This works fine but I need to use this same function for a number of different properties on this controller as well as others. As such, I extracted a helper function:

# math_helpers.js.coffee
class App.mathHelpers
  @sumArrayForProperty: (array, propertyName) ->
    array.reduce ((previousValue, element) ->
      currentValue = element.get(propertyName)
      if isNaN(currentValue)
        previousValue
      else
        previousValue + currentValue
    ), 0


# array_controller.js.coffee
sumOfCost: (->
  App.mathHelpers.sumArrayForProperty(@, 'cost')
).property('@each.cost')

This removes a lot of duplication but I still have the cost property name in the helper method as well as the property declaration. I also have the 'decoration' of setting up a computed property in general.

What I need is something that works like Ember.computed.alias('name') but allows me to transform the object instead of just aliasing a property:

# computed_properties.js.coffee
App.computed = {}

App.computed.sumByProperty = (propertyName) ->
  Ember.computed "@each.#{propertyName}", ->
    App.mathHelpers.sumArrayForProperty(@, propertyName)

# array_controller.js.coffee
sumOfCost: App.computed.sumByProperty('cost')

This allows me to easily define a 'sum' for a property without a lot of duplication. In this application I have a lot of similar functions around computing information in arrays. Being able to easily have one function for calculation allowed me to easily unit test that function and feel confident that it would work on any other object. It also simplifies the model or controller a lot for anyone viewing the class for the first time.

Update

After publishing this, I was quickly notified that Ember.computed.sum has been added to Ember as of 1.4. This is a great addition to the core, but this example is primarily designed to show an example of how to refactor your own computed property.