Ember Data is always fetching records for route - ember.js

I just switched my application over to Ember CLI and Ember-Data (previously using Ember Model). When I transition to my employees route ember data does a GET request on the api's user route with a query as intended. However, whenever I leave this route and return it continues to perform a GET request on the api. Shouldn't these results be cached? I had a filter running on the model, but I removed it and still ran into the same issue.
Route w/ Filter:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
// This queries the server every time I visit the route
return this.store.filter('user', {type: 'employee'}, function(user) {
if(! Ember.isEmpty(user.get('roles'))) {
return user.get('roles').contains('employee');
}
});
}
});
Route w/out Filter:
import Ember from 'ember';
// This still queries the server every time I visit the route
export default Ember.Route.extend({
model: function() {
return this.store.find('user');
}
});

Passing a second parameter into the filter function, {type: 'employee'}, turns it into a findQuery + filter, and find will always execute a query request. If you want to only call a particular resource once per SPA lifetime in a particular route you can add logic to keep track of it. The basic concept goes like this:
Check if you've fetched before
If you haven't fetch the records
Save the fetched records
return the saved fetched records
Example
export default Ember.Route.extend({
model: function() {
//resultPromise will return undefined the first time... cause it isn't defined
var resultPromise = this.get('resultPromise') || this.store.find('user');
this.set('resultPromise', resultPromise);
return resultPromise;
}
});
Additionally if you've already called find you can also just use store.all('type') to get all of the records for that type in the store client side without making a call to the server.

Related

Adding Records to Ember Data Store after Ajax Success

I am building a Ember Application where I fetch a feed of Debate objects from an API end point in a route, via the following ajax call:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
$.getJSON("http://localhost:3000/api/v2/debate_feed").then(debates => {
store.push(debates);
});
}
});
Upon receiving the Debate objects I need to save them to the data store, the problem is I can not access the Ember Data store after the ajax call is complete. When I try to access the store I get the following error:
Uncaught ReferenceError: store is not defined(…)
Any ideas what the issue is?
store is not defined in the Route. You can this.get('store') to get store object. You can try the below code.
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel(transition) {
return Ember.$.getJSON("http://localhost:3000/api/v2/debate_feed").then(debates => {
this.get('store').push(debates);
});
},
model: function() {
return this.store.peekAll('debate');
}
});
But the preferred approach is,
1.Define debate model model guide
2.Define Adapter - JSONAPIAdapter or RESTAdapter.adapter guide
3.Implement API end point for GET request /debates
then you can just simply say this.store.findAll('debate') this will fetch and update it to store.
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('debate');
}
});
It should be this.store.push(...), but you really should implement the ajax call with an adapter! Also notice that the model will be empty, because you return nothing. If you want to have the model do return this.store.push(...).

Fetch data from server in chunks using ember data (like infinite scroll)

I would like to display a very long list of users (yes i suggested pagination but the client wont take it) and i would like to fetch a few records at a time.
This is mainly because creating the list on the server side is resource intensive (every field is encrypted in the database<-- again there is little i can do to change this) and retrieving 1000 records would slow down the app. so i would like to display 30 records and get the next 30 immediately afterwards (the same way infinite scroll works, without having to scroll down to fetch more records). Below is my route handler:
import Ember from 'ember';
export default Ember.Route.extend({
model(){
return this.store.query('user', {
firstname:'doe' // this comes from a textbox when filtering the list of users
pageSize:30,
page:1,
orderBy:'id'
});
}
});
To make ember and webapi play nice together, i have implemented everything here https://github.com/CrshOverride/ember-web-api
the api does not return any pagination information, but i would like to keep fetching the next page until the recordset that returns is smaller than pagesize.
Background: Im migrating from angular 1 to ember 2 (very much an ember novice) and i believe i am using ember data and i generate most stuff via ember-cli
This is something that Ember-Data doesn't provide out of the box, but is very easy to implement on your own.
// route.js
export default Ember.Route.extend({
setupController(controller) {
controller.fetchUsers(someNameOrAnother);
}
});
// controller.js
export default Ember.Controller.extend({
fetchUsers(name) {
this.set('allUsers', []);
this.fetchUsersHelper(name);
},
fetchUsersHelper(name, page = 1) {
const queryParams = {
firstname: name,
pageSize: 30,
page,
orderBy: 'id'
};
this.store.query('user', queryParams).then((users) => {
this.set('allUsers', this.get('allUsers').concat(users.toArray()));
if (users.get('length')>= queryParams.pageSize) {
this.fetchUsersHelper(name, queryParams.page + 1);
}
});
}
});

How do I reload data and update a bound element after a user clicks a button?

Why is it that when I click 'Random', the information in the template isn't reset and the data isn't update?
I have data that I want to display after a REST endpoint is successfully reached. The REST data that's returned is a random database record, so I don't need to worry about randomizing my request or anything. I only need to reach the server via that URL. In this case, the URL is: localhost:8000/api/verses/0
My handlebars template looks like this:
app/templates/verses.hbs
<div id="panel">
<h3>{{model.reference_number}}
<h3>{{model.body}}</h3>
<button {{action "getAnotherVerse"}}>Random</button>
</div>
{{outlet}}
So, when the 'Random' button is clicked, the following should be invoked:
app/controllers/verses.js
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.ObjectController.extend({
actions: {
getAnotherVerse: function() {
this.get('model').reload();
// This is where the text should be reset to the new data.
}
}
});
app/routers/verses.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('verse', '0');
}
});
When you fire getAnotherVerse you just take the current record(model) and simply reload it to fetch its latest data. I guess you want to call model method of your route once again, so model will be reset and you'll get brand new record from your server.
Move getAnotherVerse to your VersesRoute where you specify model for VersesController and try following code:
# app/routes/verses.js
model: function() {
return this.store.find('verse', '0');
},
actions: {
getAnotherVerse: function() {
this.refresh(); # beforeModel, model, afterModel, setupController will re-fire
}
}
If this still doesn't work, please try this:
# app/routes/verses.js
model: function() {
return this.store.fetch('verse', '0');
},
actions: {
getAnotherVerse: function() {
this.store.unloadAll('verse'); # I assume `verse` is your Model name
this.refresh(); # beforeModel, model, afterModel, setupController will re-fire
}
}
Your telling Ember Data to find the record with id = 0. Just guessing that your API endpoint is treating 0 as a special case and returning a record that does have an actual id.
Because Ember Data is using an identity map under the hood I'm guessing that when you call reload the data is creating a new record in the store. And therefore isn't triggering updates on the record that is being used for the model.
A better approach would be to just use
var that = this;
Ember.$.get('localhost:8000/api/verses/0')
.then(function(data) {
that.set('model', data);
});
You could push the data into the store too http://emberjs.com/guides/models/pushing-records-into-the-store/ and then it would be available if you need to find it by id later.
Another approach would be to create a custom adapter / serializer that could hide some of this, really depends on how your using ember data outside of this use case.

Computed property for the number of records in the store?

This may be abusing Ember, but I want to create a computed property for the number of items in the store.
I'm trying to prototype a UI that exists entirely client-side. I'm using fixture data with the local storage adapter. That way, I can start off with canned data, but I can also add data and have it persist across reloads.
As I'm currently working on the data layer, I've built a settings route that gives me a UI to reset various models. I would like to add a Handlebars expression like {{modelCount}} so I can see how many records there are in the store. That's quicker than using the Ember Data Chrome extension, which resets to the routes tab on every page reload.
The following will show me the number of records once, but does not change when the number of records changes:
modelCount: function() {
var self = this;
this.store.find("my_model").then(function(records) {
self.set("modelCount", records.get("length"));
});
}.property()
I get that the store is supposed to proxy an API in the real world, and find returns a promise, but that's about the limit of my knowledge. I don't know how tell Ember to that I want to know how many records there are, or if this is even a valid question.
Load the result of store.find into an Ember.ArrayController's content and then bind the length of content to modelCount. An example:
App.SomeRoute = Ember.Route.extend({
model: function(){
return this.store.find('my_model');
}
});
App.SomeController = Ember.ArrayController.extend({
modelCount: Ember.computed.alias('content.length')
});
See a working example in http://jsbin.com/iCuzIJE/1/edit.
I found a workable solution by combining the answer from #panagiotis, and a similar question, How to load multiple models sequentially in Ember JS route.
In my router, I sequentially load my models:
model: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
self.store.find("model1").then(function(model1) {
self.store.find("model2").then(function(model2) {
self.store.find("model3").then(function(model3) {
resolve({
model1: model1,
model2: model2,
model3: model3
});
});
});
});
});
},
Then in my controller, I define simple computed properties:
model1Count: function() {
return this.get("model1.length");
}.property("model1.length"),
...

How to use "needs" with nested routes / controllers in emberjs RC 2

I have a very basic route setup that allows me to first show "all" records for some object. Then if the user selects a dropdown they can filter this down using a date.
I recently upgraded to RC2 and realized that "needs" has replaced or will soon replace controllerFor.
I'm curious how I can use "needs" in the below situation where I need the nested / inner route for "records.date" to change the content for the parent "records" route when a date is selected.
What is missing from below is that inside the App.RecordsDateRoute I need to change the content of the "records" controller to be a new filter (by date this time) and everything I seem to do just dumps the handlebars template and show nothing -even when I try to use something simple like
this.controllerFor("records").set('content', App.Record.find(new Date(model.loaded)))
from within the setupController method of the RecordsDateRoute
App.Router.map(function(match) {
return this.resource("records", { path: "/" }, function() {
return this.route("date", { path: "/:date_loaded" });
});
});
App.RecordsController = Ember.ArrayController.extend({
selected: 0,
dates: Ember.computed(function() {
return App.Date.find();
}).property()
});
App.RecordsIndexRoute = Ember.Route.extend({
model: function() {
this.controllerFor("records").set("selected", 0);
return App.Record.find();
}
});
App.RecordsDateRoute = Ember.Route.extend({
model: function(params) {
//the controllerFor below seems to be working great ... but what about needs?
this.controllerFor("records").set("selected", params.date_loaded);
return App.Date.create({ loaded: params.date_loaded });
}
});
With rc2, instances of other controllers can be retrieved via "controllers.controllerName", in you case it would be this.get('controllers.records').
The "needs" declaration makes the referencing controller sort of import the reference to the other controller; in your case, the date controller would be:
App.RecordsDateRoute = Ember.Route.extend({
needs: ['records'],
model: function(params) {
this.get("controllers.records").set("selected", params.date_loaded);
return App.Date.create({ loaded: params.date_loaded });
}
});
Regarding App.Record.find(new Date(model.loaded)), find() expects an id or an object whose keys and values will be used to filter the collection of models, but you're giving it a Javascript date.
Did you mean App.Record.find(new App.Date(model.loaded)), or maybe something like App.Record.find({ loaded: model.loaded }) /* assuming it's already a Date */?
There is also an initController(controller, model) method in the route called , maybe you could use that instead of "overloading" the model() method with too many responsibilities. http://emberjs.com/api/classes/Ember.Route.html#method_setupController
I recently upgraded to RC2 and realized that "needs" has replaced or will soon replace controllerFor.
To access another controller from route hooks you should continue to use controllerFor. Controller.needs is for communication between controllers, it replaces the now deprecated use of controllerFor method on controllers. AFAIK there is no plan to deprecate controllerFor on ember Routes.
I'm curious how I can use "needs" in the below situation where I need the nested / inner route for "records.date" to change the content for the parent "records" route when a date is selected.
For this use case it would be best to stick with controllerFor. It is possible to use needs this way, by specifying that App.RecordsDateController needs = ['records'] you could access the records controller via controller.get('controllers.records') from within your route's setupController hook.
What is missing from below is that inside the App.RecordsDateRoute I need to change the content of the "records" controller to be a new filter (by date this time) and everything I seem to do just dumps the handlebars template and show nothing -even when I try to use something simple like this.controllerFor("records").set('content', App.Record.find(new Date(model.loaded))) from within the setupController method of the RecordsDateRoute
App.RecordsDateRoute = Ember.Route.extend({
model: function(params) {
return App.Date.create({ loaded: params.date_loaded });
},
setupController: function(controller, model) {
var recordsController = this.controllerFor("records");
// Moved this from model hook, since here you are 'setting up a controller'
recordsController.set("selected", model.date_loaded);
// Set query based on current route's model
var query = { loaded: model.loaded };
recordsController.set("content", App.Record.find(query));
}
});