Skip to content

Instantly share code, notes, and snippets.

@Joelkang
Last active May 22, 2018 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Joelkang/0aaf87bac41e8d1215139521c7d3c4cb to your computer and use it in GitHub Desktop.
Save Joelkang/0aaf87bac41e8d1215139521c7d3c4cb to your computer and use it in GitHub Desktop.

Introduction

Controllers are a little big of a black sheep in Ember. At one point people thought controllers were going away, and were told not to use them. Since it's unclear what the state of routeable components are, controllers are very much here to stay until that story unfolds. As such, this gist aims to provide some clarity on what controllers are (or should be), and how to use them.

Are Controllers are deprecated?

Nope. ObjectController and ArrayController are deprecated, but the Controller class exists and supersedes them. Controllers sit between your routes and the top level templates used to render those routes. While a route may be the place that obtains your data, a controller is the abstraction on top of that data that you can use to massage the data for presentational purposes. My favorite example of a controller is to combine two lists into a single array for looping.

// controllers/someRoute.js
import Ember from 'ember';
export default Ember.Controller.extend({
  unifiedList: Ember.computed('model.listOne.@each', 'model.listTwo.@each', function () {
    let listOne = this.get('model.listOne');
    let listTwo = this.get('model.listTwo');
    return listOne.reduce(function(previous, next) {
      previous.push({
        primary: next,
        secondary: listTwo.findBy('id', next.get('id'))
      });
      return previous;
    }, []);
  })
});
{{!templates/someRoute.hbs}}
{{#each unifiedList as |listItem|}}
  {{listItem.primary}}: {{listItem.secondary}}
{{/each}}

Ultimately, when routeable components land, there'll be an easy path for deprecating controllers altogether. Minimally, most of the code you write in controllers will look exactly the same in the Javascript file of any Component that will replace it, so cleaning these up in future will be easy. Why make life difficult for yourself now by not using them?

Must I must specify a controller for every route?

Nope, if you don't provide a controller (by placing it in controllers/routeName.js, a default one is instantialised for you at runtime. If you need to render more data than is available in the response from the model() hook, you can set these up in the setupController() hook:

// routes/myRoute.js
import Ember from 'ember';
export default Ember.Route.extend({
  model() {
    return {
      foo: 1,
      bar: 2
    };
  },
  setupController(controller, model) {
    // Don't forget to call _super() here, or you won't have access to the data returned in model();
    this._super(...arguments);
    controller.set('bat', 3);
  }
});
{{!templates/myRoute.hbs!}}
Now I have access to {{model.foo}}, {{model.bar}} and {{bat}}

How much setup you do in this hook is obviously up to your discretion. If you find that you're setting a lot of values in this hook, perhaps you might be better off with a more declarative Controller class. The biggest caveat here is that you can't establish computed properties in the setupController() hook. If you need to set up any computed properties, you need to actually create a controller class and declare them in that file. There's also a corresponding resetController() hook that you can use to do any cleanup.

Do I put actions on the controller or the route?

It depends. As per the regular Ember architecture, actions are only accessible to one level below the class they are declared on. Controllers may call sendAction() to call the actions declared on their routes, and the route's template.hbs may trigger actions using the {{action}} helper only if the action is declared on the controller that is used to render the template.

The implementation details of these actions also matter somewhat, since in Ember.Routes, you have methods like transitionTo() while in Ember.Controllers you will need to call the corresponding transitionToRoute(). Of course, you can always return true; in your controller's action to bubble it up to the router for it to handle. But that means you now have to create 2 actions of the same name, one in the controller and one in the route.

Enter the very aptly named ember-route-action-helper addon, which provides a way to access from your templates any action defined on the route that rendered said template. Instead of writing {{action "controllerActionName"}}, you can write {{route-action "routeActionName}} and bypass declaring the action on the controller altogether. Since controllers tend to be dumb and don't do much, while routes handle fetching data and transitioning, top level templates will typically prefer calling route actions to update the model or fetch some new dataset. This way, you can keep your controllers lean such that when it's time to deprecate them, there will not be much to deprecate at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment