Search code examples
javascriptember.jsember-router

Ember.js Router/controller properties


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.


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.