Delaying route change until data is loaded in Ember - ember.js

In Angular the $routeProvider resolve property allows delaying of route change until data is loaded. Given the route model hook in Ember returns a promise I was wondering how that stuff was done in Ember
Here it's what I mean in angular Delaying AngularJS route change until model loaded to prevent flicker
A link with a sample code would be great

Just recently this PR introduced Async transitions to ember.js. With this change you can do all sort of things, like for example delaying a route's transition if data is still underway. A route has now all sorts of hooks available to do just want you want.
As an example (taken from the gist mentioned below) in the afterModel hook you could do something like this to only transition to the post.show route if you actually have data:
App.PostsIndexRoute = Ember.Route.extend({
afterModel: function(posts, transition) {
if (posts.length === 1) {
this.transitionTo('post.show', posts[0]);
}
}
});
Since this new features are still very young you need to use the latest master to have it available. For more info on how to use the API please see this gist.
Hope it helps

Related

Ember.js: parallel loading parent and child models

I have these routes:
posts
index (all the posts)
single (with dynamic parameter post_id)
index (single post and it's comments view)
edit (editing post)
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.
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 post and the comments are not loaded in parallel.
The resources aren't cached if a user transitions between the routes through a third one.
It mixes visual design with data loading.
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;
}
});

How do you update a controller from another controller in ember.js?

In my application I have common header that outlets into the main application layout. In that header you can select a site. I need that site selection to update another template that is rendered in the application layout. Having struggled with this for a few days I think the correct way to solve this is to use a shared service, to have the header controller observe the site selection and set that value in the shared service, then to have the index controller use a Ember.computed.alias on the value in the service controller. The following is an example of my code:
controllers/header.js
import Ember from 'ember';
export default Ember.Controller.extend({
sessionService: Ember.inject.service(),
currentSiteChanged: Ember.observer('session.current_site', function(){
var current_site = this.get('session.current_site');
console.log('currentSiteObserver', current_site);
this.get('sessionService').set('currentSite', current_site);
}),
});
controllers/index.js
import Ember from 'ember';
export default Ember.Controller.extend({
sessionService: Ember.inject.service(),
currentSite: Ember.computed.alias('sessionService.currentSite'),
dashboardData: function(){
var currentSite = this.get('currentSite');
console.log("in dashboardData", currentSite);
//other code that uses the currentSite
}.property('currentSite'),
});
services/session-service.js
import Ember from 'ember';
export default Ember.Service.extend({
currentSite: null,
setCurrentSite: function(){
var currentSite = this.get('session.current_site');
this.set('currentSite', currentSite );
}.on('init'),
});
I think this should allow someone to select a site in the header and have the dashboardData property in index update to use that selection. When the page initially loads the header defaults to the first site and the index renders it has the correct site value that it must have gotten from the session-service, however if you select another site the index does not get updated. Via the console.logs and debugging I can see that the header is observing the change and setting the value on the session-service.
Additionally I have tried solving this other ways (injecting the header service into the index and observing a property, injecting the index in the header and directly setting the value, sending and listening to events,etc) but I am willing to try anything or to be corrected that this isn't the correct way to solve the problem.
I am using ember 1.13.8 and moving to 2 isn't an option at the moment.
I don't think a service is an appropriate solution to this problem.
You want your application to have a good RESTful url design (respect for urls is a corner-stone of the Ember framework), so try to capture your application state in the URL.
Consider that if a user were to select a site, and then hit refresh they would lose their selection unless you stored it somehow in a cookie or localStorage.
I would recommend using either routes or query parameters to solve your problem.
Routes
Using routes is fairly straightforward (http://whatever.com/sites/pet-hampsters).
Query Params
You can also use query params, something like this http://whatever.com/?site=pet%20hampsters.
To do this you would write an action that bubbles up to your application controller and sets the value of the 'site' queryParam. Any of your sub-controllers on the currently active route can then read the site value with the new Ember.inject syntax. This is the conventional way to manage dependencies between controllers.

Url not updating when new model is saved

router.js
this.route('claim', function() {
this.route('new');
this.route('edit', { path: '/:claim_id' });
});
claim.new -> redirect to claim.edit with an empty model. this.transitionTo('claim.edit', model);
Initially, because the model is not saved we get: index.html#/claim/null
When we save the model, it continues to display as index.html#/claim/null instead of say index.html#/claim/87
Is there a way to force the url to refresh?
The only way to force the URL to refresh is to force the route/page to refresh. You can use the refresh method in your route to do that.
To understand why it doesn't update automatically, you have to understand how routes work and how Ember is separate from Ember Data (or any data persistence library).
First, you have to remember that Ember has no knowledge of Ember Data and the specifics of its object model. It just sees plain Ember objects. Ember Data models are more important than other models to you, but Ember isn't aware of that distinction.
Second, the routing in Ember is binding/observer aware. It doesn't watch for changes and update URL state accordingly. It calculates the URL once using the serializer hook, then it leaves it as that. It's not going to recalculate if your model changes in any way. It will only recalculate when it's refreshed.
Short story long, the part of your application that changes the ID of your model and the part that calculates the URL to use aren't aware of each other. You'll have to manually sync them up. Using the refresh method is probably easiest way to do that.
UPDATE: As Kitler pointed out, you can also transition to your route using the new model. It won't fire the model hook so it won't have to do any asynchronous work, but it will still update the URL.

Custom Ember Api Endpoint

I am trying to figure out how to get data from a custom api. I am using Ember 1.8.1, Ember Data 1.0.0-beta.12 and Ember CLI
In my router i have the following resource
this.resource("communities", {path: '/communities/:community-id/follow-ups'}, function() {});
I have my model defined for the correct response. In my communities router I am trying to get the data from the api like so
this.store.find('community', params['community-id']);
The problem I am having is that I am trying to retrive data from the api endpoint
/communities/{community-id}/follow-ups
But the app is trying to grab the data from
/communities/{community-id}
How do I define the custom resource route to pull from the follow-ups
The router path isn't going to change where the API makes the call to, that just helps Ember change the browser path.
You might want to consider using an adapter if you really need it to hit .../follow-ups.
You'd want to make a CommunitiesAdapter I think. ember g adapter communities, or community, not sure offhand.
And I think the function on it you're looking for is pathForType.
Check it out at http://guides.emberjs.com/v1.10.0/models/customizing-adapters/
You can create a custom adapter for your model in particular but deep nesting on routes can be tricky in Ember and not worth the time if you are in a rush.
Try setting the model of the route directly with a get json
App.NAMERoute = Ember.Route.extend({
model : function(params){
return Ember.$.getJSON(window.apiHost+'/communities/'+params.group_id+'/follow-ups');
}
});
Sometimes simple solutions is what you need

request process in emberjs

I am new guy to emberjs, I want to know flow of request in emberjs.
There are some query related to emberjs:
what is specific role of controller in emberjs, wherever I have seen we can create action in template.
There is standard naming convention and association between controller, routes and view, but how can to associate a controller, routes and views.
how to flow control when a request process?
a routes handover control to a controller or
a controller handover control to routes.
if we want to associate a controller and a routes manually then how to associate.
what is specific role of controller in emberjs, wherever I have seen
we can create action in template.
Controller is connecting your model with view like in MVC pattern. In Ember.JS you can use controller to keep your logic that will be used on one particular module, manage dependencies or store conditions. In templates you can use only simple conditions (without and/or), so whenever you need some more complex condition you should put in inside controller. For instance
App.PersonController = Ember.ObjectController.extend({
isPersonRich: function() {
return #get('person.money') > 1000000 && #get('person.isReal')
}.property('person.money', 'person.isReal')
});
So I person that is not fictional and have more 1 000 000 assets is rich.
{{#if isPersonRich}}
<p>This person is rich!</p>
{{/if}}
There is standard naming convention and association between
controller, routes and view, but how can to associate a controller,
routes and views.
Route usually fetch data from your backend.
App.PersonRoute = Ember.Route.extend({
model: function(params) {
this.store.find('person', params.person_id);
}
});
Each time when you enter the persons route ember is going to make call to your api (using ember data in this case) to find given person. Moreover, it's going to display loading route in this case and give you some fallback after failure.
PersonView would be the place where you can put your jQuery code that is going to be executed after template would be successfully rendered.
App.PersonView = Ember.View.extend({
didInsertElement: function() {
this.$().find('*[data-toggle=tooltip]').tooltip();
}
});
In this example I am adding bootstrap tooltip to template.
how to flow control when a request process?
Route is procceded before controller, you have even setupController method inside each route that set model to the controller by default.
if we want to associate a controller and a routes manually then how to
associate.
You can overwrite setupController method and eventually renderTemplate. There is nothing more to do. I advice you to stick to ember name conventions.
Additionaly take a look that if your controller is not going to handle fired action it is going to propagate to your route.