Multiple outlets in Ember.js v2 router-Collection of common programming errors

I’m just getting started with Ember.js and one thing that seems promising is the idea of multiple outlets combining multiple templates mixed together to produce a complex but modular layout.

I can’t get it to work, though. It seems like there were many questions, answers, and examples on this a few months ago (mid 2012) but in the march to 1.0 they very recently (December 2012/January 2013) rewrote the router to a “v2” API. The docs are good at what they do describe but omit a lot of big picture context, and I have yet to find a single end-to-end example.

Here’s what I’ve read:

  • everything under the Routing guide (up to date, but not exhaustive)
  • “outlet” template helper api reference (this may be out of date? Every attempt I’ve made to call controller.connectOutlet() fails with Uncaught TypeError: Object has no method 'connectOutlet'.
  • announcement of Ember.js Router API v2. Specifically the bottom couple comments (question and answer on multiple outlets). Yes, this gist is marked “Warning; outdated; for up-to-date information see the routing guide”. But the current routing guide doesn’t seem to completely describe the behavior. The Rendering a template section of the guide shows how to render to different outlets that already exist (and I can get this to work), but I can’t figure out how to connect additional outlets or instantiate additional templates.

What does work for me:

  • Setting up nested routes (well, nested resources; you can’t nest routes; but you can customize routes for the nested resources), and nesting templates and outlets that are automatically instantiated according to the routes.

What I have not been able to figure out how to accomplish:

  • Manually instantiate templates and connect them to outlets. This seems necessary if you want to use multiple outlets, or if you want to have a structure your outlet/template relationships differently than your routes. (There will be an example of this below. Essentially what I’m trying to do is use a template as a mixin that I can embed wherever else I want.)

The thing that seems promising but fails for me is

  • Override a route’s controller (extend the route using App.WhateverRoute = Ember.Route.extend(), supply my own setupController method) and call controller.connectOutlet here. This fails as described above; the controller object passed into this method does not have a connectOutlet method.

Example (here as a jsFiddle, or below as a self-contained html document which embeds the CSS and scripts and loads Ember and dependencies from https links, so you should be able to just save to a local file and open in a browser if you want to try it):



  
    Ember.js Router Example
    
      .outlet {
        border: 1px solid black;
        padding: 5px;
      }
    
  

  

    
    
    


    
      
Root index template. You should not see this because we redirect App.IndexRoute elsewhere.


About this demo.


Guide to this demo.


Animals. You have selected:

{{ outlet }}
No animal selected.


Cat. I can meow. Like all animals, I {{ outlet }}


Dog. I can bark. Like all animals, I {{ outlet }}


am alive.


Select contents for my outlet: {{#linkTo "index"}}/ (root){{/linkTo}} {{#linkTo "about"}}/about{{/linkTo}} {{#linkTo "guide"}}/guide{{/linkTo}} {{#linkTo "animals"}}/animals{{/linkTo}} {{#linkTo "animals.cats"}}/animals/cats{{/linkTo}} {{#linkTo "animals.dogs"}}/animals/dogs{{/linkTo}}

{{ outlet }} App = Ember.Application.create(); App.Router.map(function() { this.resource("about"); this.resource("guide"); this.resource("animals", function() { this.route("cats"); this.route("dogs"); }) }); App.IndexRoute = Ember.Route.extend({ redirect: function() { this.transitionTo('about'); } }); App.AnimalsIndexRoute = Ember.Route.extend({ redirect: function() { this.transitionTo('animals.cats'); } }); App.AnimalsCatsRoute = Ember.Route.extend({ setupController: function(controller, model) { // BUG: this controller object has no connectOutlet method // (uncomment to see this yourself) // controller.connectOutlet('animal_mixin'); } }); App.initialize();

Essentially animal_mixin is a chunk of boilerplate that I want to use repeatedly as a mixin, dropping it wherever I want by putting an outlet there and connecting it to this template. I realize this example is contrived, because I could do it with “inheritance” provided by the nesting structure: the contents of animal_mixin could go directly in the “animals” template, and I wouldn’t need to mention it in animals/cats and animals/dogs. That would be fine if I wanted it in all animals, but let’s say I had another subroute of /animals that I don’t want to include this snippet. Again, the example is contrived but I hope the question and the intent are clear.

  1. You can use multiple named outlets. Here’s a jsfiddle example: http://jsfiddle.net/W2dE4/6/.

    
        {{outlet header}}
        {{outlet body}}
        {{outlet navBar}}
    
    

    Also see this answer for some other techniques.

    events: {
        showModal: function(){
            this.render('modal', {
                into: 'index',
                outlet: 'modalOutlet',
                controller: this.controllerFor('modal')
            }); 
        }
    }
    

Originally posted 2013-11-23 09:50:46.