Issues with model when navigating while still being retrieved - ember.js

Working with an older ember application (2.18.1). The following problem is repeated too many times to all fix in the time frame I got available right now.
The component is loading it's own data (setting this.get('model')) and all works fine.
However as the database is now a little slower the user sometimes click on one link, where the template render the component and it start loading it's data .
If the user click another link (to a route that does exactly the same) data from both the previous and the "new" component get loaded.
I can't reset the model when data get loaded, since the fetchRecord method that loads the data get called over and over with paging (as the user scroll down).
I'm sure I'm just not thinking of an obvious solution (did not work on Ember for a few years), any advise?
(ps: some of these components does not use paging, in mean time I'm going to clear out the model before setting it to what the api returns)

I'm afraid ember-data gives no support to abort a request, but you can handle that yourself directly on your component calling the endpoint through Ajax or fetch, and then pushing the payload or aborting requests using the lifecycle hooks. For example, you can trigger the abort() on the willDestroyElement hook.
import $ from 'jquery';
import Component from '#ember/component';
export default Component.extend({
init() {
this._super(...argument);
const xhr = $.get( "ajax/test.html", (data) => {
this.get('store').pushPayload(data);
});
this.set('xhr', xhr);
}
willDestroyElement() {
this._super(...argument);
this.get('xhr').abort()
}
});

Related

Ember2.8 : Refresh absolutely everything on transitionToRoute('/route')

So I currently have a card game that when finished loads up a modal with a button to transition back to the home route. This is the action that is called when the modal's close button is clicked.
goBackHome() {
this.transitionToRoute('/games');
},
It does redirect to the requested route, but all the changes to back-end data I did still remain. I've also tried passing it the model itself
goBackHome() {
this.transitionToRoute('games');
},
Basically, it doesn't do a hard refresh of everything. What I would like is when transitionToRoute is called to reload the route as if I was inputting the exact URL into the browser myself.
Well I try to write possible way to do this, hope you can pick one and solve your problem.
you need to add this.refresh(); to either your method or in model() to force reload the model to get new data (depends on what you need)! if you want to reload the location the whole windows I mean just add location.reload();
one example should be
model(id){
var post = this.get('store').find('post', id); // Find the post from the store
post.reload(); // Force a reload
return post; // Return the fetched post NOW and the information will be updated.
}
Moreover, you can do something else in your model() look at the example :
model(params) {
return this.store.findRecord('whatever', params.id, { reload: true });
}
this { reload: true } enforces the model to get fresh data, it can be implemented with findAll as well.
ref: At the moment I'm just using the afterModel hook to force a reload, but it would be good to get the reload flag working again.
so, you can do also another way which is you can send a param to the function and get that in route and in route actions you can have this.referesh()! to reload the model. In this case, you need to add this.sendAction('actionName'); and in route actionName(){this.referesh();}

Ember link-to gets active class for the wrong transition

This is one of those Ember issues that I'm unable to replicate anywhere but my project. The visual effect of the problem is that the active class on a link-to is one transition behind. I'll click a link and the link that goes to the page I was just on is highlighted with the active class.
I've started digging into the link-to component code to figure out how active is computed. But it is based on _routing.currentState and I'm not sure what that is. The currentState, and other bits of info, are passed to the routing's isActiveForRoute which then calls the routerState's isActiveIntent. And that function calls another isActiveIntent and compares some more things together. All this seems like a large easter egg hunt for something (the root of my problem) that is probably not in Ember's code anyways.
I feel like the following snippet sums up the problem I'm having. The targetRouteName is the route that is being directed to by the link. _routing.currentRouteName seems to be pointing to the route the browser is currently looking at. The fact these match makes me feel like the link should be active, but the active function returns false.
> link.get('targetRouteName')
"parentRoute.pageA.index”
> link.get('_routing.currentRouteName')
"parentRoute.pageA.index”
> link.get('active')
false
For reference this is after finding the link via the Chrome extension and showing all components. I then did link = $E.
For the wrong link (the one that does get the active class) I get:
> link.get('targetRouteName')
"parentRoute.pageB.index"
> link.get('_routing.currentRouteName')
"parentRoute.pageA.index"
> link.get('active')
"active"
Additional Raw Information
The routes I'm dealing with are nested. But it is a pretty standard nesting, very much like the one I have in my ember-twiddle (e.g. page-a, page-b, page-c).
There is a model hook on the parent route and on the indexs of the children routes. But the children routes reference (this.modelFor(...)) the parent.
My template is referencing the .index of those routes. They are standard link-to components. They do not include model information.
I'm running Ember-cli 1.13.8, Ember 2.0.0, and Ember Data 2.0.0-beta.1.
What I have tried so far
Upgrading to 1.13.0
Moving the file structure to pods
Removing the functions in my authentication route which a lot of these routes inherit from.
Upgrading to 2.0.0
Trying to remove/add .index on my routes
Tried replicating on ember-twiddle
Doing ember init with ember-cli to see if my router or application setup was different from the standard layout and it doesn't differ in any significant way.
Adding model information to one of the links, that didn't change anything and since it didn't call the model hooks it messed up the view.
Asked on the slack channel
Please Help
I've had this issue for a couple weeks now and I'm not sure where else to look. I'd love any suggestions on how I can resolve this.
Update
This ended up getting fixed in 2.1.0.
This is common problem when you mess around with willTransition router action. For example,
IMS.ResultDetailsEditRoute = Ember.Route.extend({
actions: {
willTransition: function() {
this.controller.clearForm();
}
}
});
In this code snipped willTransition called controller's method "clearForm()" which no longer exists. For some reason, Ember doesn't throw an error, but it causes the problem that #RyanJM explained.
I have run into something similar when using a component with a nav. Here was my approach:
I added a controller (I know, you should be steering away form these, but I needed to). My controller:
import Ember from 'ember';
const {
Controller,
inject
} = Ember;
export default Controller.extend({
application: inject.controller(),
});
Then, in my template, I could pass application to my component.
{{account/account-icon-nav currentRouteName=application.currentRouteName}}
In my component, I set set up a function to test my current route names:
import Ember from 'ember';
const {
Component,
computed,
get
} = Ember;
const activeParentRoute = function(dependentKey, parentRouteName) {
return computed(dependentKey, {
get() {
return get(this, dependentKey).indexOf(parentRouteName) > -1;
}
});
};
export default Component.extend({
isYourProfile: activeParentRoute('currentRouteName', 'account.your-profile'),
isYourActivity: activeParentRoute('currentRouteName', 'account.your-activity'),
isYourGoals: activeParentRoute('currentRouteName', 'account.your-goals')
});
Then bind the active class yourself:
<div class="icon-nav md-hidden">
{{link-to "" "account.your-profile" classBinding=":profile isYourProfile:active" title="Your Life"}}
{{link-to "" "account.your-activity" classBinding=":activity isYourActivity:active" title="Your Money"}}
{{link-to "" "account.your-goals" classBinding=":goals isYourGoals:active" title="Your Goals"}}
</div>
I know this is a bit different since we are doing it within a component, but I hope it helps. You can bind these classes yourself by passing the application around.

Ember DS.Store.findAll() returns empty collection

Hey I need to modify some records which I get from the DataStore. If I add the following code in my router I can see that the requests get passed to my template, but I can't modify each request of the collection because the collection is empty.
model() {
return this.store.findAll('user').then(function(users) {
console.log(users.get('length')); // 0
return users;
});
}
I thought that the promise gets resolved when all the records have been fetched from the server but this doesn't seem to be the case. Or did I completely miss something.
I also tried to modify the model in the afterModel callback with the same result.
I'm using Ember 1.13.0 (with Ember-CLI), Ember-Data 1.13.4 and ember-cli-mirage for Mocking my HTTP Requests.
UPDATE:
I managed to create a workaround for this issue. In my controller, I created a new property which listens for model.#each and then I was able to modify model and pass it to the view.
export default Ember.Controller.extend({
users: function() {
return this.get('model.users').filter(function(user) {
// The Promise is resolved twice
// The first time with an empty model and the second time with
// the actual data. So I filter the empty model.
return user.get('id');
}).map(function(user) {
// do fancy stuff with our user
return user
});
}.property('model.#each')
});
Ember Data 1.13
So after spending some time on this topic i found the solution to this issue. It's basically the way how ember works. So under the hood findAll is returning two promises.
findAll without data in the store
find records from the store (resolve first promise -> length 0,
because no data is in the store)
fetch new data in the background (resolves second promise)
findAll with data in the store
find records from the store (resolve first promise with cached data)
fetch new data in the background (resolves second promise with new
data)
If you want to wait for all the data to be loaded you can use query which is returning only one promise.
model() {
return this.store.query('user', {});
}
For findRecord I found the following workaround, which is only working if your backend supports any kind of filtering on the id of your record.
model() {
return this.store.query('user', {
'filter[id]': 1
}).then((users) => {
return users.objectAt(0);
});
}
You can have a look on the following discussion on github
Ember Data 2.0
On Ember Data 2.0 this issue is resolved.
First you should make sure the data is coming in from Mirage as you expect. Open your Ember inspector and verify the models made it into your store. If not, you likely have a problem with the format of the JSON response from your mock route.
To diagnose, check out your console for a log of the JSON response, and ensure it matches what you expect. If you have a custom route handler in your /mirage/config.js for this route, you could also put a debugger statement in there and verify the data is what you think it should be.
If you're using default Ember Data 1.13, it probably means you're using the JSON API serializer/adapter. Is this what you intend? What is the backend for this app ultimately going to look like? If it's going to be JSON API, you'll need to do a bit more work in the Mirage config.js file for now, something like
this.get('/contacts', function(db, request) {
return {
data: db.contacts.map(attrs => {
type: 'contacts',
id: attrs.id,
attributes: attrs
})
};
});
I had a similar problem to the one you are describing when using Ember and Ember data version 1.13. At the time, I was reading the updated documentation of Ember 2.0 without Ember Data 2.0. Once I upgraded both libraries I was able to get the behavior you are trying to achieve with the first code snippet. Namely, the promise is handled correctly with nonzero records with ember data 2.0.

Load single objects from "index" allways

Using the latest revision of Ember-Data and the RESTAdapter, is there a way of doing the following?
I have a resource called App and a API that responds to /apps by returning the correct JSON (with { apps: [...] }etc.)
Since this gets served from a static json on our server, it is quiet inappropriate to create server-side resources for every app that can be fetched as /apps/:app_id. Instead, it would be good if the RESTAdapter allways loaded /apps, even if it then only uses one single app out of the fetched ones.
Do I have to write my own Adapter to achieve this? If yes, what would be a good point to "hook into"?
Supposing you have an app model App.App, it should be enough to call App.App.find() when your application loads. This will make the AJAX call to /apps. Even if you don't cache the result in a variable, your data store will be populated with the returned records. Now whenever you call App.App.find(id), Ember Data will check your store and return the record if it has it. If it doesn't have the record, then it will try to call /apps/:id, but this shouldn't happen if your application is designed to use only a static collection.
There are a few different places you could put the App.App.find() call. I would probably put it in App.ready:
App = Ember.Application.create({
ready: function() {
// pre-load apps
App.App.find();
}
});
App.App = DS.Model.extend({
//...
});
It seems a little hacky (and probably is), but it looks like one can achieve this by overwriting the DS.Adapter:find().
In my case, to block calls to /app/:app_id I wrote this:
find: function(store, type, id) {
// Terminate calls for single app
if (type === App.App) {
// instead, load all apps and drop this request
App.App.find();
return;
}
// or continue as usual
this._super(store, type, id);
}
This also works when you have a hierarchy of embedded: 'always' records and Ember thinks it has to load a middle level. Just make sure you load the parent for sure when dropping requests like this!

Is there a way to get a callback when Ember.js has finished loading everything?

I am building an Ember.js app and I need to do some additional setup after everything is rendered/loaded.
Is there a way to get such a callback? Thanks!
There are also several functions defined on Views that can be overloaded and which will be called automatically. These include willInsertElement(), didInsertElement(), afterRender(), etc.
In particular I find didInsertElement() a useful time to run code that in a regular object-oriented system would be run in the constructor.
You can use the ready property of Ember.Application.
example from http://awardwinningfjords.com/2011/12/27/emberjs-collections.html:
// Setup a global namespace for our code.
Twitter = Em.Application.create({
// When everything is loaded.
ready: function() {
// Start polling Twitter
setInterval(function() {
Twitter.searchResults.refresh();
}, 2000);
// The default search is empty, let's find some cats.
Twitter.searchResults.set("query", "cats");
// Call the superclass's `ready` method.
this._super();
}
});
LazyBoy's answer is what you want to do, but it will work differently than you think. The phrasing of your question highlights an interesting point about Ember.
In your question you specified that you wanted a callback after the views were rendered. However, for good 'Ember' style, you should use the 'ready' callback which fires after the application is initialized, but before the views are rendered.
The important conceptual point is that after the callback updates the data-model you should then let Ember update the views.
Letting ember update the view is mostly straightforward. There are some edge cases where it's necessary to use 'didFoo' callbacks to avoid state-transition flickers in the view. (E.g., avoid showing "no items found" for 0.2 seconds.)
If that doesn't work for you, you might also investigate the 'onLoad' callback.
You can use jQuery ajax callbacks for this:
$(document).ajaxStart(function(){ console.log("ajax started")})
$(document).ajaxStop(function(){ console.log("ajax stopped")})
This will work for all ajax requests.
I simply put this into the Application Route
actions: {
loading: function(transition, route) {
this.controllerFor('application').set('isLoading', true);
this.router.on('didTransition', this, function(){
this.controllerFor('application').set('isLoading', false);
});
}
}
And then anywhere in my template I can enable and disable loading stuff using {{#if isLoading}} or I can add special jQuery events inside the actual loading action.
Very simple but effective.