Skip to content

Instantly share code, notes, and snippets.

@ghempton
Created July 18, 2012 21:16
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ghempton/3138957 to your computer and use it in GitHub Desktop.
Save ghempton/3138957 to your computer and use it in GitHub Desktop.
Ember.js Controller Brainstorm

Ember.js Controller Brainstorm

Currently, there are several awkward points to Ember.js controllers. Namely:

  1. Passing controllers around to view's not instantiated by the routing system is hard.
  2. Non-singleton controllers are very difficult to manage (e.g. #each where each view has it's own controller).
  3. Sharing data between controllers is difficult.

Solution for 1 and 2: Controllers are lazily created by views

Consider the following observations:

  1. Controllers should not reference each other.
  2. Each controller corresponds to a single view or parent-child hierarchy of views.

Based on the the above two points, there is no need for controllers to be "injected" at application initialization time. As it stands, the controller is only set once in the connectOutlets method and it's view property is only set to a single view. There is no need for the controller to exist outside of the lifecycle of a view.

A trivial implementation of this would be to just have the view's controller property do the instantiation:

  controller: Ember.computed(function(key, value) {
    var parentView, controllerClass;

    if (arguments.length === 2) {
      return value;
    } else if (controllerClass = get(this, 'controllerClass')) {
      return controllerClass.create({view: this, router: get(this, 'router')});
    } else {
      parentView = get(this, 'parentView');
      return parentView ? get(parentView, 'controller') : null;
    }
  }).property().cacheable(),

The controller could also be destroyed with the view.

Solution for 3: Data (not controllers) is injected

Although controllers should not reference each other, the underlying data is shared amongst controllers. Examples of such data:

  1. The current user
  2. Application settings
  3. The root model object in a hierarchy of routes/controllers (e.g. '/post/:post_id', '/post/:post_id/comments', '/post/:post_id/edit', '/post/:post_id/contributors/:contributor_id')

In the last example, all of those routes could potentially point to different controllers, and all of the controllers could presumably need access to the same underlying post model.

The solution here could be to inject the model into the controllers.

A trivial implementation of this would be to just set the model on an object that is used as an injection context inside of connectOutlets:

  post: Em.Route.extend({
    connectOutlets: function(router, post) {
      set(router.injectionContext, 'post', post)
      [...]
    },
  })

Then in the controller:

App.PostsController = Em.Controller.extend({
  postBinding: 'router.injectionContext.post'
  [...]
});

App.CommentController = Em.Controller.extend({
  postBinding: 'router.injectionContext.post'
  [...]
});
@juggy
Copy link

juggy commented Jul 19, 2012

Having the view create the controllers feels weird.

@trek
Copy link

trek commented Jul 19, 2012

I've definitely felt some of this pain too.

@ghempton
Copy link
Author

I agree that having the view contain the controller creation logic is slightly weird. Another alternative would be for the view to request a controller from the router. E.g. the controller computed property would call router.createController(this) or something.

@MikeAski
Copy link

+1 for point 2. Would really be excited to have it...

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