Ember.js Router/controller properties

ember-router, ember.js, javascript

Solution

As answered here, `Ember.Route` has a `model` function which allows you to set an object or a collection of objects as the model of that route. Routes that deal with a single object should have a controller that extends `Ember.ObjectController` while routes that deal with a collection of objects should have a controller that extends `Ember.ArrayController`. Subsequently, in the `Route` workflow, the data coming from the `model` hook is set into the controller's `content` property via `setupController` hook.

The routes have their own workflow to setup their controllers, so by default this method will be called and populate the content with the model. Consider the following:

fiddle

App.Email = DS.Model.extend({
    address: DS.attr('string'),
    isActive: DS.attr('boolean')
});

App.Router.map(function() {
    this.resource('emails', function() {
        this.route('email', {path: ':email_id'});
    });
});

App.EmailsRoute = Ember.Route.extend({
    model: function() {
        return App.Email.find();
    }
});
App.EmailRoute = Ember.Route.extend({
    model: function(params) {
        return App.Email.find(params.email_id);
    }
});

App.EmailsController = Ember.ArrayController.extend();
App.EmailController = Ember.ObjectController.extend();

The framework should generate the default code for these routes in order to setup the controller, which would look like this (and you can override if you want):

App.EmailsRoute = Ember.Route.extend({
    ...
    setupController: function(controller, model) {
        controller.set('content', model);
    }
    ...
});

There are cases (see question/answer linked above) in which you may need/want to override these methods to do something different than the default functionality, for example:

fiddle

App.EmailsRoute = Ember.Route.extend({
    model: function(params) {
        return [{id: 1, address: 'other@email.com'}];
    },
    setupController: function(controller, model) {
        // here, controller is whatever controller this route needs
        // by conventions, it knows it should be EmailsController
        // of the type ArrayController
        // model is whatever was returned by the model function above

        // the content is a "bag" which can be filled with a model or any
        // other object you need. Just keep in mind your view layer will
        // be referring to this object later on
        controller.set('content', model);

        // you can set other properties of the controller here too
        controller.set('applyFilter', true);
    }
});

Now the templates will be able to access the data in the controller. The example below iterates through a collection of objects (`App.Email`) in the `EmailsController`. Any public attribute in this collection or in its child objects are accessible here, one example is `{{email.address}}`:

<script type="text/x-handlebars" data-template-name="emails">
    <ul>
    {{#each email in controller}}
        <li>
            {{#linkTo emails.email email}}
                {{email.address}}
            {{/linkTo}}
        </li>    
    {{/each}}
    </ul>
    {{outlet}}
</script>

Note that the template is not talking directly to the model, but rather to the `content`, which was assigned with data from the model. Like I said, you can stash any object in the content or model via routes, so you're not tied to use `DS.Model` nor the architecture is strongly coupled.

If this model, instead of `App.Email` type, had a different type with different attributes, it would also be accessible here, with limitations tho. If an attribute of the model is a collection, it cannot be accessed through index (e.g. `{{email.messages[0].body}}` wouldn't work). The best course of action in this case, would be a computed property in the controller which would give you direct access to the first item of the messages collection of the email, if it had one.

Problem

I'm trying to wrap my head around the `model` property or a route vs. the `content` property of the controller. If you set the `model` property in a route, does this automatically set it to the `content` property in a generated controller. Also, I think the `content` property of the controller allows you to access the attributes of that object in the template, is that true? I read the docs and still am having trouble digesting some of these conventions.

Original source