Complex router with lots of outlets

Nothing nothing nothing

Try looking into the render helper, rather than outlet. This has the benefit of instantiating the proper view and controller when you call it. You can seed it with data by fetching the data in your afterModel route - good place to be fetching supporting models if you haven’t visited the route yet, Remember that it is not breaking any rules if you want to manually push data into your store.

Where would you store the reference to these supporting models? I’ve been using setupController rather than afterModel so that I can assign them to the controller. Is this not best practice?

afterModel is promise aware, but setupController isn’t. You should have all your data in the store before setting up controllers.

I think setupController should be promise aware though, if not the whole router. It’s the first place everyone reaches out to put this sort of stuff all the time.

And where do you store the supporting model promises in afterModel? I have properties on the controller, but there’s no controller reference yet, am I wrong?


  var App.FooRoute = Em.Route.extend({
  activate: function(){
    this.generateController('bar')
  },
  model: function(){
    return this.store.find('foo')
  },
  afterModel: function(model, transition) {
   var self = this;
   return self.store.find('bar').then(function(bars){
      self.controllerFor('bar').set('model', bars);     
    })
  },
  setupController: function(controller, model) {
    controller.set('needs', ['bar'])
  },
})

Thank you for the example.

By promise-aware, do you mean that the fact that the transition will block until the promise is resolved?

If you’re using the render helper you’re probably best off adding your additional models to the controller for the route and then passing through to the render helper i.e.

var App.FooRoute = Em.Route.extend({
  model: function(){
    return this.store.find('foo')
  },
  setupController: function(controller, model) {
    controller.set("model", model);
    controller.set("bar", this.store.find('bar'));
  },
})

// foo.hbs
{{render 'bar' bar}}

The thing is that setupController isn’t promise aware, so whether it passes or fails, ember will continue and do its own thing. This can bring a lot of timing related bugs, like with relationships or with route transitions.

e- my bad, I meant to reply t @opsb. But yeah, Its safer to fetch data on promise aware hooks.

That’s an interesting point. Currently I always proceed to the route’s view and if there’s any issues I’d show them there. If you use hooks that support promises what do you do in the case of error? From your code I don’t see any explicit error handling so I presume you’d just remain on the previous view?

@opsb Ember gives you a free error route, as well as a loading route at the top level, so you shouldn’t really have to declare anything, it’s one of those things where Ember generates the code for you. I sorta think I remember you can get more granular, like nesting error and loading routes under resources.

That does sound useful, I’ll look into it. It does look like a promise aware version of setupController would clean things up a lot here though, then you could do something like:

var App.FooRoute = Em.Route.extend({
  model: function(){
    return this.store.find('foo')
  },

  setupController: function(controller, model) {
    return controller.when({
      model: model,
      bar: self.store.find('bar')
    });
  },
})

ok, that controller.when function doesn’t exist, but it could…

Yeah, that looks badass and deals with a lot of model relationship problems. I’m thinking that the whole router should be promise aware, like even renderView, promises shouldn’t be limited to just data, there’s plenty of times I have to use a promise on a dom element.

@opsb Dig into RSVP a little more, you can do exactly that and keep the promises in the model hook.

var App.FooRoute = Em.Route.extend({
  model: function(){
    var promises = {
      model: this.store.find('foo'),
      bar: this.store.find('bar')
    }

    return Ember.RSVP.hash(promises);
  },

  setupController: function(controller, promises) {
    controller.setProperties(promises);
  }
});

If fetching the supporting models requires a property of your main model you can do something like this:

model: function(params) {
    var self = this,
          personPromise = this.store.find('person', params.person_id);

    personPromise.then(function(person) {
      var promises = {
        model: new Ember.RSVP.Promise(function(resolve) { resolve(person); }),
        sameFirstname: self.store.find('person', { firstName: person.firstName }), 
        sameLastname: self.store.find('person', { lastName: person.lastName }), 
      }

      return Ember.RSVP.hash(promises);
    });

    return personPromise;
  }
1 Like

Wow don’t know how I missed that one, very useful, thanks!

To return the favour, I think

new Ember.RSVP.Promise(function(resolve) { resolve(person); })

can be

Ember.RSVP.resolve(person)

It’s cleaner to link supporting models to their own controllers though. Controllers are long lived. It separates concerns cleaner,you can use stuff like needs, etc, and putting everything in one hook is going to make the surface area too big and there’s already some callback hell there.

Thank you very much. I have been stuck on this for weeks.