Skip to content

Instantly share code, notes, and snippets.

@darthdeus
Created January 26, 2013 01:09
Show Gist options
  • Save darthdeus/4639391 to your computer and use it in GitHub Desktop.
Save darthdeus/4639391 to your computer and use it in GitHub Desktop.

Controller 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.Controller.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.Controller.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 -->
{{controllers.foo}}

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:

  • visit /posts/1
  • router basically does App.Post.find(1) and assigns that to the content of PostController
  • template post is rendered
  • template post.index is rendered in post's outlet

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.Controller.extend({
  needs: ["post"]
})

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

{{controllers.post.content}}

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.

@mehulkar
Copy link

👍

@ahawkins
Copy link

{{controllers.post.content}} should simply be {{post}}. Bind accordingly.

@seanabrahams
Copy link

👍

@stefanpenner
Copy link

@twintubo if its common to bind against controllers.post, maybe needs should synthesis a top level post property directly on the host controller

@darthdeus another thing to consider is typeInjections, the work similar to needs, but without the runtime dependency validation. And rather the the lazy controllers.<otherController> lookup, they link the controllers at creation.

I am in the progress of some related work, trying to consolidate the ember di/container ideas, into some simple guides. So if u your anyone has ideas/concerns/questions ping me

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