Reputation: 75
I have these routes:
There are two separate requests for fetching post by ID and fetching post comments by post ID. I want to load post and comments for posts.single.index route in parallel, because I have a post ID in route name and I do not have to wait when post will be loaded.
But Ember loads posts.single model, and only AFTER post it loads comments.
Is it possible to call child model in parallel with parent model?
I have found a solution when posts.single does not load anything, and posts.single.index calls two requests in it's own model. On the other hand, I should load post model within all posts.single child routes, such as posts.single.edit. It could be a problem, when application will be grow.
Upvotes: 0
Views: 316
Reputation: 6338
There are several techniques to load multiple resources in a model
-Hook of a Route. Which ones best fits your needs or are even possible highly depends on your application. Especially the capabilities of the backend API used and if your Ember application uses Ember Data or plain fetch/ajax requests makes a big difference.
The simplest case is using Ember Data together with an REST API that follows JSON:API specification and supports inclusion of related resources:
import Route from '@ember/routing/route';
export default Route.extend({
model({ post_id }) {
return this.store.findRecord('post', post_id, { include: 'comments' });
}
});
If you are using plain fetch
, you could use Promise.all()
to load multiple records in parallel:
import Route from '@ember/routing/route';
export default Route.extend({
async model({ post_id }) {
let [post, comments] = await Promise.all([
fetch(`/posts/${post_id}`),
fetch(`/posts/${post_id}/comments`),
]);
return { post, comments };
}
});
If you don't like the Promise.all()
syntax with array destructing you might want to have a look at RSVP.hash()
. rsvp
is bundled with ember by default.
If doing it with Ember Data but your API does not support side-loading, it's a little bit more tricky as you need to use a query to load the comments. It depends on your adapter configuration but I guess it would look like this:
import Route from '@ember/routing/route';
export default Route.extend({
async model({ post_id }) {
let [post, comments] = await Promise.all([
this.store.findRecord('post', post_id),
this.store.query('comment', {
filter: {
post: post_id
}
})
]);
return { post, comments };
}
});
You can't use multiple model
-Hooks to load resources in parallel. model
-Hooks of parent and child routes are executed in order by design. The model
-Hook of a child route won't be fired before a Promise
returned by it's parent model
-Hook is resolved. Having that said there are still techniques to only load needed data and caching data that is shared between different child routes.
Let's take your example from the question and more detailed in the comments to this answer: Our frontend should show a post including it's comments on one route and a form to edit the same post on another route. Both routes need the same post resource but only one also needs the post's comment. The application should not load the post again if user transition from one route to the other and should not load the comments if user transitions to edit view.
A naive attempt would be a parent route that loads the post
resource and two child routes, one for the view including comments and one for the edit form. I call this attempt "naive" cause it's failing in three ways:
The third point may be confusing. Indeed it's a common misunderstanding of nested routes in Ember. They are not meant to model data hierarchy but should be used to share visual UI between subroutes. It's not the model
-Hook what count but the rendering of the child templates in parent's {{outlet}}
.
All of your concerns could be easily solved by a service that caches resources client-side. This one of the main features of Ember Data. That's also one of the most hyped features of Apollo client for GraphQL. It's as simple as that: Most complex frontend applications need a client-side cache layer to prevent overfetching of resources. If you face that requirement I would recommend to use one of the existing solutions. For simple use cases you could also build you own service. A basic implementation could look like:
import Service from '@ember/service';
export default class StoreService extend Sevice({
postCache = new Map();
loadPost(id) {
let { postCache } = this;
if (postCache.has(id)) {
return postCache.get(id);
}
let request = fetch(`/posts/${id}`);
postCache.set(id, request);
return request;
}
});
Upvotes: 2