Ember

Intermediate Ember Controller Concepts

This post was updated on 2/18/2014 to reflect changes in Ember and incorporate feedback from some of the comments.

I’ve been finding issues in an Ember application I’m working on where the author (me) didn’t fully grasp Ember controllers. Here are a few controller concepts I’ve learned.

Controller Concepts

  • A controller is a decorator for your models. It proxies all the get and set calls to the underlying model unless that property is defined on the controller itself.

  • Singleton controllers are long-lived objects that hold onto application state. Models change beneath the controller, but the controller is never destroyed.

  • Item controllers are short-lived objects. These controllers are often used to decorate an array of items and are destroyed and created during the life of an application.

  • You don’t necessarily need to create your own controller, especially if you’re not holding onto application state. The router actions object is a great place to save, create, and delete models.

A Day in the Life of a Controller

In this example I’m going to show a list of items and let the user select one at a time. This kind of UI is often seen in a mater-detail view where the URL keeps track of the selected item, but let’s say you’re just selecting an item in the list for now. This is a pretty common use case where controllers shine.

To get started we’ll setup a route with some dummy data and render a list of names.

App.IndexRoute = Ember.Route.extend
  model: ->
    [
      {firstName: 'Gut', lastName: 'Bomb'}
      {firstName: 'Rock', lastName: 'Tastic'}
      {firstName: 'The', lastName: 'Fish'}
    ]
<ul>
  {{#each}}
    <li>
      {{firstName}} {{lastName}}
    </li>
  {{/each}}
</ul>

If you’ve used Ember much this probably isn’t earth shattering stuff.

But now it gets interesting. When the user clicks on an item we want it to appear selected.

<ul>
  {{#each itemController="name"}}
    <li {{action "select"}} {{bind-attr class="isSelected"}}>
      {{firstName}} {{lastName}}
    </li>
  {{/each}}
</ul>
App.NameController = Ember.ObjectController.extend
  isSelected: false

  actions:
    select: ->
      @set('isSelected', true)

First we’re telling each to use the nameController to wrap each name. Then we send the select action to the nameController when an item is clicked. The select action changes the isSelected property which in turn adds the is-selected class to the li element.

Notice the declared isSelected property at the top of the controller. It’s a good idea to declare this property, otherwise isSelected would be set on the model.

But oh no! If we click another item it also gets selected without unselecting the other one. Try it out.

We need a way to unselect names. Let’s move the state about which item is selected to the parent array controller.

App.IndexController = Ember.ArrayController.extend
  selectedName: null

  actions:
    selectName: (name) ->
      @set('selectedName', name)

App.NameController = Ember.ObjectController.extend
  needs: ['index']

  isSelected: (->
    @get('controllers.index.selectedName') == @get('model')
  ).property('controllers.index.selectedName')

By using a computed property on the parent array controller we can codify the idea that only one item is selected at once. We’re taking advantage of the fact that actions bubble through the controllers and moving the select action to the IndexController so that the logic can live close to the state it manipulates.

The needs declaration opens a line of communication from the NameController to the IndexController. One could instead use the parentController property on the NameController and trade in the explicit needs setup for more terse syntax.

Setting the selectedName on the IndexController is not strictly necessary, but it’s a good habit to get into to make sure that you aren’t accidentally proxying a property to another object. It also helps developers see the state a controller is responsible for at a glance.

With these changes we continue to keep the application state out of our models and still remember the selected item as users move around the application. The IndexController is a singleton controller that remembers which names is selected, while the NameController is a short-lived decorator that presents the isSelected property to the template.

So now check it out. It works!

Want a more in-depth look at Ember? Check out the comprehensive Introduction to Ember.js online class that I’ve been updating with Ember’s creators.

Blog ad  test icon

Subscribe to the Gaslight Newsletter

Want more content like this? Subscribe to Gaslight’s monthly email newsletter for coding tips, tech insights, events, news and more.

Subscribe Now