I met a requirement which needs multiple data from different ajax requests in one route. And I want the data is prepared before view rendering. Now I can do that partially, but I want to hear your suggestions about what is the best practice.
For simplicity I use $.get in demo code. And in my project I don’t use ED or other model layer libs, just simple encapsulation for $.ajax.
My requirement is like this:
I have a “edit user” page. In this page I can edit a user and assign it with a role. So I need two data: user and role list. At beginning I get user data in model hook and role list in setupController.
1st solution
App.Router.map(function() {
this.route('user', {path: '/user/:user_id'});
});
App.UserRoute = Em.Route.extend({
model: function(params) {
return $.get('/users/'+params.user_id+'.json');
},
setupController: function(controller, model) {
controller.set('model', model);
$.get('/role_list.json').then(function(roles) {
controller.set('roleList', roles);
});
}
});
But in this way, I find that the role list is loaded after view rendering. That’s not what I want.
Because only beforeModel, model, afterModel hook support promise, and before the promise is resolved, the setupController is not called and view is not rendered. I start thinking about getting all data in model hook. And Em.RSVP seems a good choice.
2nd solution
App.UserRoute = Em.Route.extend({
model: function(params) {
return Em.RSVP.hash({
user: $.get('/users/'+params.user_id+'.json'),
roleList: $.get('/role_list.json')
});
},
setupController: function(controller, model) {
controller.set('model', model.user);
controller.set('roleList', model.roleList);
}
});
It accomplish the task for me: getting all data before view rendering. But since the route contains dynamic segments, one thing breaks this solution: link-to.
I have this link in my template:
{{#link-to "user" user}}Some user{{/link-to}}
The model hook is called only when the route is entered via URL, if the route is entered by click link-to link, It passes the model from link-to to that route.
So in my case, when the route is entered via URL, the model parameter for setupController is the result of Em.RSVP which is a hash (a plain JavaScript object). when the route is entered via link-to link, the model parameter for setupController is the model link-to give it to. It’s bad, and since model hook is not called, I can not get the role list.
But there is another way. even model hook is not called, afterModel hook is always called and it also support promise. So the 3rd solution is to get role list in afterModel hook.
3rd solution
App.UserRoute = Em.Route.extend({
model: function(params) {
return $.get('/users/'+params.user_id+'.json');
},
afterModel: function(model, transition) {
return $.get('/role_list.json').then(function(roleList) {
// Because I can not pass role list to setupController, I have to set it as a model property.
model.get('roleList', roleList);
});
},
setupController: function(controller, model) {
controller.set('model', model);
}
});
It works, but not a best practice. As you can see there is no way to pass role list to setupController, because setupController only get the model from model hook or link-to. I have to set it as model property. Another way is to set the role list to a route property and get it in setupController, and then clear the route property. But I think it’s also not a good solution.
What I’m thinking about is, setupController is used for getting multiple data and set multiple controllers. Currently we can set multiple controllers, but we don’t have a good way to get all data before view rendering.
We can only get main data for a route (user in this case) before view rendering, and for the rest data, we have to get them after view is rendered, and before that, we need to display “Loading” everywhere.
What I think a good solution, is to make setupController to also support promise in future.
Do you have any idea about this situation?