Jakub Arnold's Blog

Ember.js: Controller's Needs Explained

Since the v2 router came it became clear that using global singleton controllers like App.userController = App.UserController.create() is not the way to go. This prevents us from doing a simple binding like

App.UserController = Ember.ObjectController.extend({
  accountsBinding: "App.accountsController.content"

There is no need or even possibility to manage the controller instances with the new router though. It will create the instance for us. One way we can use this is with this.controllerFor, which can be used inside of a route.

App.UserRoute = Ember.Route.extend({
  setupController: function(controller, model) {
    // some magic with `this.controllerFor("user")`

but since this method is only available on the route and not inside a controller, it wasn’t very pleasant to specify dependencies (or needs) between controllers. Which is exactly where needs come in and solve the issue

App.UserController = Ember.ObjectController.extend({
  needs: ["foo"]

this will give you the opportunity to call controllers.foo on the App.UserController instance and get back an instance of App.FooController. You could even (ab)use that in the templates like this

<!-- inside `users` template -->
{% raw %}{{controllers.foo}}{% endraw %}

Needs vs routing

Needs become incredibly useful when you have nested routes, for example

App.Router.map(function() {
  this.resource("post", { path: "/posts/:post_id" }, function() {
    this.route("edit", { path: "/edit" });

In this case we will get post, post.index and post.edit. If you go to /posts/1 you expect to get post.index template, which is true, but the context (or model, or content) is being set on the PostController, not on PostIndexController.

When you think about it it does make sense, because the resource is basically shared between post.index and post.edit, that’s why it is fetched and stored in their parent. Let’s go through this in detail:

and when you transition to /posts/1/edit, the only thing that changes is the leaf route, you still keep the same App.Post model, because it belongs to the parent PostRoute, not to the leaf PostIndexRoute. But this has a drawback. You’re not able to directly access the content from the post.index template, since it doesn’t belong to it’s controller. That’s where needs come in.

App.PostIndexController = Ember.ObjectController.extend({
  needs: ["post"]

and in the post/index template, you can access the content like this

{% raw %}{{controllers.post.content}}{% endraw %}

By specifying the need Ember will make sure that it gives you the right PostController instance with it’s content set to the right value.