Reputation: 6291
I have a number of different application-level models — i.e., current user, current account, etc. — that I want to load before rendering my application. How and where should this be done? This question/answer helped a lot, but it doesn't cover the async aspect.
The following code accomplishes what I want, but loading the models in beforeModel
(to take advantage of it waiting for the promise to resolve) doesn't seem right. Should I even be loading these models in ApplicationRoute
?
App.ApplicationController = Ember.Controller.extend({
currentAccount: null
});
App.ApplicationRoute = Ember.Route.extend({
beforeModel: function () {
var self = this;
return App.Account.find(...).then(function (account) {
self.controllerFor('application').set('currentAccount', account);
});
}
});
Thanks for your help!
Upvotes: 5
Views: 15427
Reputation: 1642
You want to preload data/models to initialize your application, and feel beforeModel is incorrect?
Sounds like you need an application initializer!
Your friend in this instance:
App.deferReadiness(); // halt progress of application until all instances of this call (ie: multiple initializers) are matched by an instance the following call:
App.advanceReadiness(); // consider this to be equivalent to a promise resolve call.
1) From you looking up the user directly, modifying where mentioned to suit your app setup:
Ember.Application.initializer({
name: 'loadUser',
after: 'store',
initialize: function(container, app) {
// modify this following to suit how you're determining the account
var url = 'user/' + currentAccount;
// tell the app to pause loading until advanceReadiness is declared
app.deferReadiness();
// load from JSON
Ember.$.getJSON('url').then(function(json) {
var store = container.lookup('store:main');
store.load(app.User, json);
// tell app to start progressing again
app.advanceReadiness();
});
}
});
2) Through meta tag:
Ember.Application.initializer({
name: 'currentUser'
after: 'store',
initialize: function(container, app) {
app.deferReadiness();
$(function() {
// Look up an attribute in a meta tag
var store = container.lookup('store:main'),
attributes = $('meta[name="current-user"]').attr('content');
if (attributes) {
var obj = store.load(app.User, JSON.parse(attributes)),
user = App.User.find(obj.id),
controller = container.lookup('controller:currentUser').set('content', user);
container.typeInjection('controller', 'currentUser', 'controller:currentUser');
}
app.advanceReadiness();
});
}
});
3) Through Session data:
Ember.Application.initializer({
name : 'currentUser',
after : 'session',
initialize: function(container, app) {
var controller = container.lookup('controller:currentUser');
container.typeInjection('controller', 'currentUser', 'controller:currentUser');
}
});
Upvotes: 6
Reputation: 12433
I managed to get this work by using nested Promises and the afterModel method in the ApplicationRoute.
App.ApplicationRoute = Ember.Route.extend({
model: function() {
// load the reservation (a globally needed model)
return App.Reservation.fetch().then(function(reservations) {
return reservations.get('firstObject');
});
},
afterModel: function() {
// Load all other globally needed models
var self = this;
return App.Gender.fetch().then(function(genders) {
self.controllerFor('application').set('genders', genders);
return App.FilterAttribute.fetch().then(function(filterAttributes) {
self.controllerFor('application').set('filterAttributes', filterAttributes);
//return App.SomeOtherModel...
});
});
},
setupController: function(controller, model) {
controller.set('reservation', model);
}
});
Works just perfectly :-) The application remains in the LoadingRoute until all records are loaded. Note that I am using Ember Model, but this should make no difference, it just have to return a Promise.
Upvotes: 3
Reputation: 688
The trick is to return a promise from the route's model method.
This will cause the router to transition into App.LoadingRoute route, until the promise resolves (which can be used for loading indication bars/wheels etc.)
When the promise resolves, the App.LoadingRoute will be deactivated, and the original route's setupController method will be called.
This works for ember-data promises, JQuery's $.ajax promises and ember-model's fetch promises.
Just make sure you return the actual model after resolving the promise.
This can also be a good place to handle errors if the promise is rejected - but I'll leave that to some other question.
As for where you should load your models - that is dependent on your app's usage.
Usually you would load a model where the URL indicates you need that model - a rule of thumb would be the indication of a model ID in the URL.
This of course changes if you need to prefetch some data.
And now for some code:
App.SomeRoute = Ember.Route.extend({
model: function(params){
return App.SomeModel.fetch(params.model_id).then(function(modelData){
// it is better to return the actual model here, and not the promise itself
return App.SomeModel.find(params.model_id);
});
},
setupController: function(controller, model){
controller.set("model", model);
// do some controller setup here - can be omitted if no setup is needed
// this will run only after the promise has been resolved.
}
});
App.LoadingRoute = Ember.Route.extend({
activate: function(){
this._super();
// add some loading indication here
},
deactivate: function(){
this._super();
// remove loading indication
}
}
Hope this helps.
Upvotes: 6