Loading additional data with a model

Is there a best-practices method for dealing with pages that have other data that must be sideloaded with the actual model?

Options I’ve explored, none of which is perfect:

Return a RSVP.Hash in the model hook, set up the controller manually

Pros: You can load everything at the same time.

Con 1: This confusingly breaks {{link-to}}s where the target of the link is a model object rather than an ID, since the model hook is skipped in this case.

Con 2: If you refactor your model hook like this, where previously it resolved a model by itself, any child routes that call this.modelFor('yourRoute') now need to be refactored too.

Fetch all data in the model hook, but resolve only the “real” model, and somehow finagle the other objects into the controller.

Pros: As above. Fixes Con 2 from above.

Cons: Still breaks {{link-to}}s, but in an even more confusing way, since your routes may load seemingly okay, but be missing data.

Fetch the data in an after/beforeModel hook

Pros: Fixes all the big issues listed above. These hooks are called no matter what, so we can always load the data if needed.

Con 1: Feels hacky. Involves saving the resolved objects on the route, then taking them out in the setupController hook.

Con 2: Data is loaded synchronously - the beforeModel promise needs to resolve because moving onto the model hook - unless you make this even hackier by starting the request in beforeModel, then waiting for it to resolve in afterModel.

Load the additional data asynchronously after the page loads

Pros: Faster load times.

Cons: Can make your application harder to understand. Need to worry about more states each page can be in. Can break some Ember components, like select boxes.

2 Likes

I’ve found the one-model-per-route heuristic to be most useful. Surprisingly there is a similar rule of thumb in the rails world - one model per controller (see Sandi Metz’s thoughts on this). It keeps things simple and organized. If your route needs multiple sets of data, typically these data are related in some way, which means there’s a new object dying to be created to manage the relationships among this data.

I often even add the related data as methods on the “main” model. For example if I was writing a route where a User could edit their profile, and they needed to be able to edit their Country, you may write something like

// pods/user/edit/route.js
export default Ember.Routes.extend({
  model(params) {
    return Ember.RSVP.Hash({
      user: this.store.find('user', params.user_id),
      countries: this.store.find('country')
    });
  }
});

but then you run into the problems you described. Instead, create a method on the User model called availableCountries:

export default DS.Model.extend({
  availableCountries: Ember.computed(function() {
    return this.store.find('country');
  }
});

Now, you can just return the user in the model, and in the form, bind the content of a <select> to user.availableCountries.

2 Likes

Thanks for the response. I’ll ruminate on this for a bit, but I think that approach makes the most sense.

How exactly does this work? When exactly will the availableCountries() method be resolved? In Rails code, I would do something like that, but there I don’t have to deal with async stuff.

Since I’m having a very similar issue I’d be really glad if somebody could explain to me how async methods on a model really work. this.store.find returns a promise, so I’m not sure how I would use that in a template. are such values automatically resolved before the template is rendered?

On Jul 17, 2015, at 11:40 AM, Fryie noreply@emberjs.com wrote:

Sorry for the delay, feel free to @ me in the future so I get an e-mail.

It would be async in this case, yes, so you could simply write your templates to prepare for that, e.g. {{#if model.availableCountries}} // ui {{else}} Loading... {{/if}}.

Alternatively you could have your server send up the additional resources side-loaded when your app requests the main model.

1 Like

thanks. so the template will auto-update as soon as the resources are loaded?

Yep that’s right. If model.availableCountries or if model.availableCountries.length, something like that

1 Like