Skip to content

Ember Controller versus ObjectController

by cory on February 17th, 2014
When doing Ember.js training, I commonly see confusion surrounding the different controller types that Ember offers. In this post I'll show a couple examples that illustrate the differences between the two.

Every time you you display a template in an ember application, you can use Handlebar's interpolation expressions to display property values in the rendered HTML (often called data binding). The source of these property values is the controller that Ember is using as the context for the handlebars template. Every time you display a template, its context is bound to some controller, either one that you have explicitly created or one that ember has auto-generated for you (you can see the generated controllers logged to the console if you create your app with "LOG_ACTIVE_GENERATION: true").

Ember.Controller



For example, if you have the template "Hello, {{name}}.", and a controller with the property `name: "Cory"`, then you'll see "Hello, Cory." You can see that below

Ember Starter Kit

Any property that is on the controller can be displayed by referencing it in the template. Properties can be simple key-value pairs as above, or the result of a function. When using a function as a property for display, you must add a `.property()` call to the end of the function definition. Pass any dependent keys as arguments to the `property()` call. For example, we can make a more sophisticated controller and template like this:

App.IndexController = Ember.Controller.extend({
  greeting: function(){
    if (this.get('isFancy')) {
      return "Greetings";
    } else {
      return "Hello";
    }
  }.property('isFancy'),
  name: "Cory"
});

Ember.TEMPLATES['index'] = Ember.Handlebars.compile("{{greeting}}, {{name}}");


This uses the controller's property `isFancy` to determine what type of greeting to display. Since we haven't specified `isFancy` explicitly yet, it will be falsy and the output of the template will still be "Hello, Cory".

If we explicitly define `isFancy` to be true, however, the output changes to "Salutations, Cory."

So far these examples all show usage of the standard controller, which we extended from `Ember.Controller`. There's another type of controller Ember offers, called the `ObjectController`, which we'll look at now.

Ember.ObjectController

A typical use case in Ember is to load in model data via an ajax call and display some of it to the user in the template. Typically the data is loaded in and returned from the `model` hook in the route. We'll make that change now, explicitly defining a route in our code instead of letting Ember auto-generate it as before. (If you have `LOG_ACTIVE_GENERATION` set to true, you'd see something like "generated -> route:index" in the console, pointing out that the index route had been automatically generated.)

App.IndexRoute = Ember.Route.extend({
  model: function(){
    var author = Ember.Object.create({name: 'Cory'});
    return author;
  }
});


This example isn't loading in data via ajax, but it will have the same result regardless. The model hook just has to return an object or a promise that resolves to an object.

Since we are planning on displaying model data in our template, it's time to change our controller to an ObjectController. Ember will automatically set the "model" property on our controller to the return value of the matching route's `model` hook. In this case we return the `author` object from `IndexRoute`'s `model` hook, so it will be set to the "model" property of the IndexController. We can now wire up our original starting template using the ObjectController instead. Knowing that its "model" property is the author object, along with what we learned earlier about property functions on a controller, we could get the same behavior we started with by doing this:

App.IndexRoute = Ember.Route.extend({
  model: function(){
    var author = Ember.Object.create({name: 'Cory'});
    return author;
  }
});

App.IndexController = Ember.ObjectController.extend({
  name: function(){
    return this.get('model.name');
  }.property('model.name')
});

Ember.TEMPLATES['index'] = Ember.Handlebars.compile("Hello, {{name}}");


This feels a little awkward and luckily Ember has anticipated this use case already and offers us a much terser option. The ObjectController is so named because it proxies property lookups to its `model` property (its "object", so to speak). When our template references the `name` property, the ObjectController's default behavior is to look up the `name` property on its model, exactly as we have just manually done above. This means we can remove all of this code and instead say:

App.IndexController = Ember.ObjectController.extend({});


The output is unchanged. Ember Starter Kit

Proxying Properties to the ObjectController's Model

Earlier I said that the ObjectController proxied property lookups to its model, and although this is correct there's an important caveat to mention: The ObjectController will first check whether the requested property has been defined on it. Only if the property has not been defined will it look up the property value on the model.

As an example, consider setting a `name` property on the ObjectController explicitly. What would happen in that case?

App.IndexController = Ember.ObjectController.extend({
  name: "impostor"
});
Ember Starter Kit

The index controller will intercept the lookup of the `name` property and return its own `name` property, effectively masking the `name` property on the model. This usually isn't desired behavior and can be the source of some confusion. Most of the time we will want to add properties to an ObjectController it will be to display different data that is not the model's direct concern. A very common example of this would be to display different items in the interface when the controller has been switched into "edit mode" (i.e., had its "isEditing" flag set to true) by the user. The `isEditing` property isn't the model's concern, it's a controller concern.

To continue building on our example, we'll once again make our code show a fancy greeting. This time, though, we'll assume that whether or not a fancy greeting should be displayed is the concern of the user viewing our app, not something that the author object itself ought to be concerned with. A perfect opportunity to augment the controller itself.

Adding the `isFancy` simple boolean property, and the `greeting` property function that we used before, onto the ObjectController allows us to show the fancier greeting once again. Here's the code, with a checkbox to toggle the "isFancy" property

Ember Starter Kit

I hope this illustrates the difference between normal Ember controllers and ObjectControllers, as well as how to use both. There are a few subtle ways to get tripped up when using one or the other that I'll list below.

Gotchas

Using a model hook in the route but not using an ObjectController

If you have set up a route to return a model but not set the corresponding controller to be an ObjectController, the properties you reference in your template will be looked up on the controller directly, resulting in nothing displaying in your template. This can be an easy one to overlook, especially as Ember will happily render your template without raising any errors about the missing properties.

Ember Starter Kit

Overriding setupController but not Setting the Controller's Model

We didn't cover it here, but in a Route you can define a `setupController` hook to do extra setup work on the controller. Ember's default controller setup behavior, as mentioned above, is to set the "model" property on the controller to the result of the "model" hook on the route. If you override the `setupController` hook, however, Ember won't do this default behavior for you anymore.

For example, suppose we decided to add a `setupController` hook to our fancy greeter's IndexRoute to set the "isFancy" property to true when entering this route? We could add this hook to our route like so:

setupController: function(controller, model) {
  controller.set('isFancy', true);
}


Unfortunately, although this does make it so the fancy checkbox is checked, we've lost the display of the name:

Ember Starter Kit

The solution is to either set the "model" property explicitly or just call `this._super(controller, model)` and allow Ember's default `setupController` behavior to happen. Here is is, fixed:

Ember Starter Kit

If you have any related questions, I'm @bantic on twitter. Get in touch. :)

From → general

One Comment
  1. Andrey permalink

    Thank you for this post, Cory! I had exactly the problem of using the Controller and not the ObjectController - your perfect explanation helped me very much!

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS