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?