Should we use controllers?

Last reviewed in October 2015 with Ember 2.5 Update to Ember Octane in progress!

Controllers are going away and components & services are taking over. This is part of an effort to improve the framework architecture (and lessen the cognitive struggle?) in Ember.

Despite the good news, Ember is in state of flux and controllers are still a concept we must be familiar with. They are labeled as thing of the past yet are a crucial building block today.

This “mixed message” is causing frustration.

What's the place of controllers in a “services and components” world? Best practices going forward?

First things first: What is a controller?

A controller is a routable object meant to “decorate” a model with display logic. Example:

export default Ember.Controller.extend({
  emphasizeName() {
    return `HEY ${this.get('model.firstName')} !!!`
  },
  isVisible: Ember.computed.and('model.firstName', 'model.lastName')  
})

You wouldn't put emphasizeName in your model, would you? This is clearly a display logic concern.

{{#if isVisible}}
  <span>{{ emphasizeName }} Kill the controller!</span>
{{/if}}

Every template in our applications is backed by a controller. Every property in a template is a property of its controller.

Deprecated confusion

If a controller handles display logic, this is where you'd handle events and interact with the DOM, correct?

Wrong. The infamous View was used for that purpose. Views have been removed from Ember 2.0, henceforth all that kind of interaction happens in components.

ArrayController and ObjectController (two decrepit classes) were used ages ago to proxy the model, such that controller.firstName would delegate to model.firstName. Modern-day controllers (extending Ember.Controller) do not, so you have to explicitly call model.firstName.

While there were obviously good intentions behind these ideas, they essentially contributed to blur concepts and cause confusion.

Coming back to our example, what does “routable” mean and how did that model end up in the controller?

diagram

That's right, a route loads the model and then calls the setupController method, that by default is:

export default Ember.Route.extend({

  model() {
    // returns a model
  },

  setupController(controller, model) {
    controller.set('model', model);
  }

})

Not only do controllers hold template state. They are singletons and, as such, are conveniently used to keep longer lived state (like sessions that persist across requests). Were used, I mean.

The obsolete needs API allowed us to require any other controller and access its properties, for example this.get('controllers.position.progress').

We now use Services singletons to store long-lived state, which are injectable via Ember.inject.service().

Says the latest official guide:

Controllers are very much like components, so much so that in future versions of Ember, controllers will be replaced entirely with components.

Octane news & best practices, straight to your inbox?

Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)

What is a component?

It's true that controllers are like components. In a way. Components are isolated by design and are fully responsible for a portion of the UI.

In other words, it has the functions of a template (with its own template), a controller (in that it holds state “backing” that template) and a view (event handling and DOM-related interaction).

diagram

Should we still use controllers?

The latest Ember guides state that “modern Ember applications don't often use controllers” and that “controllers will be replaced entirely with components”.

Let's listen to what Ember creators Tom and Yehuda have to say about this subject:

The idea then is to minimize controller usage. They are still needed mainly for two reasons:

The day routable components land in Ember, we will be able to route a URL/model directly to a component. Until then, we can employ this pattern:

diagram

This temporary shim layer is used as a glue between a route and a “top-level” component. Once a model has reached that top-level component, it will eventually propagate the model throughout its child component hierarchy.

Octane news & best practices, straight to your inbox?

Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)

Translation example

For most migrations, renaming export default Ember.Controller.extend(...) to export default Ember.Component.extend(...) will just work, as components partly have the role of controllers.

How about an example to illustrate this better?

Before

App.AlbumController = Ember.ObjectController.extend({

  needs: ['audio'],

  equalizer: function() {
    var genre = this.get('genre');
    return this.get('controllers.audio').equalizerFor(genre);
  }.observes('knob'),

  // ...

});

After

This could be roughly translated to newer Ember versions as:

{{!-- app/template/album.hbs --}}

{{ album-knobs model=model }}
// app/components/album-knobs.js

export default Ember.Component.extend({

  audio: Ember.inject.service(),

  equalizer() {
    const genre = this.get('model.genre');
    return this.get('audio').equalizerFor(genre);
  }.observes('knob')

});

// app/services/audio.js

export default Ember.Service.extend({
  equalizerFor(genre) {
    // ...
  }
});

We:

  • changed the proxying controller (ObjectController)
  • prepended model. to model properties – remember we're not proxying it anymore
  • used the “controller/template shim” to pass the model on to a component from a route
  • replaced needs + AudioController for an injected AudioService

(This is a super simple example I just made up!)

Wrapping up, the conclusion is: minimize the usage of controllers. Use them only for the bare minimum: to forward a model to a “top-level” component (as “shim layer”) and/or for query params. When routable components arrive, it will be a matter of a few adjustments.

I hope this post helps you strengthen the bridge between “old” and “new” Ember. Any questions? Anything I can improve? Let me know in the comments!

Enjoyed this article? Join Snacks!

Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)