Synchronous data calls in Ember.js - javascript

I am trying to do the following when visiting reviews/show (/reviews/:id):
Load two models from the server: One review and one user.
I only have access to the review's id, so I need to load it first, to get the userId
And then, when that has finished loading and I now have the userId, query the user using the userId attribute from the review
Finally, return both of these in a hash so I can use them both in the template
So two synchronous database queries, and then return them both at once in the model hook of the route.
I don't mind if it's slow, it's fast enough for now and I need it to work now.
This is what I've tried and it doesn't work:
reviews/show.js
export default Ember.Route.extend({
model: function(params) {
var user;
var review = this.store.findRecord('review', params.id).then(
function(result) {
user = this.store.findRecord('user', result.get('userId'));
}
);
return Ember.RSVP.hash({
review: review,
user: user
});
}
});

You can do this:
export default Ember.Route.extend({
model: function(params) {
var reviewPromise = this.store.findRecord('review', params.id);
return Ember.RSVP.hash({
review: reviewPromise,
user: reviewPromise.then(review => {
return this.store.findRecord('user', review.get('userId'));
})
});
}
});
The reason why user is undefined is because it hasn't been assigned in your first promise until the review is resolved, but Ember.RSVP.hash has received user as undefined.

Related

Ember.js. Filtered model

I have a model with tasks, and i wantto get data filtered by status and show result in different lists.
so i have a construction with does't work as i want.
tasks: Ember.computed(function(){
var modelTasks = this.get('store').findAll('task');
return {
todo: modelTasks.filterBy('status', 'todo'),
inProgress: modelTasks.filterBy('status', 'inprogress'),
done: modelTasks.filterBy('status', 'done')
};
}),
I'm new, so please be tolerant.
Why do you need tasks computed property?.
findAll returns Promise so your code is not correct.
Async computed properties little tricky - read this ignite article for more info.
I would say, data fetching should happen at the route level, so corresponding route js file model hook you can write,
export default Ember.Route.extend({
model() {
return this.get('store').findAll('task').then((result) => {
return {
todo: result.filterBy('status', 'todo'),
inProgress: result.filterBy('status', 'inprogress'),
done: result.filterBy('status', 'done')
};
});
}
});
inside corresponding hbs file, you can access it like model.todo

Ember Nested Route and Promises

I have a jquery ajax call defined like this
var fetchMessages = function(){$.getJSON(<some url>).then(function(data){ return data; }};
var messages = fecthMessages();
My routes are setup like this
App.Router.map(function() {
this.resource('messages', function() {
this.resource('message', { path: ':message_id' });
});
});
I use the promise messages in my routes like this
App.MessagesRoute = Ember.Route.extend({
model : function(){
return messages;
}
});
The above route works fine.
Next I have a nested route like shown below. This however errors out when I directly try to visit #/messages/<id of the message>. Loading #/messages followed by visiting #/messages/<id of message> works fine.
App.MessageRoute = Ember.Route.extend({
model: function(params) {
message = messages.findBy("id", params.message_id);
return message;
}
});
So how do I handle the promises in nested routes?
So how do I handle the promises in nested routes?
Apparently Ember handles these for you.
This however errors out when I directly try to visit #/messages/:
App.MessageRoute = Ember.Route.extend({
model: function(params) {
message = messages.findBy("id", params.message_id);
return message;
}
});
messages is still a promise, not an array; it doesn't have a findBy method. Instead, use
return messsages.then(function(m) {
return m.findBy("id", params.message_id);
});

Reloading ember data from server

I'm trying to reload store data from the server after transitioning to a page.
The transition i'm referring in this case, is from an index page, to a specific page by id.
here is my code:
App.Board = DS.Model.extend({
title: DS.attr('string'),
boardItems: DS.hasMany('BoardItem'),
});
App.BoardItem = DS.Model.extend({
title: DS.attr('string'),
board: DS.belongsTo('Board')
});
App.BoardIndexRoute = Ember.Route.extend({
model: function() {
return {
title: 'Boards List',
boardItems: this.store.find('board')
}
}
});
App.BoardShowRoute = Ember.Route.extend({
model: function(params) {
// this.store.reloadRecord('BoardItem'); - tried this, didn't work :(
var boardData = this.store.findById('board', params.board_id);
return boardData;
}
});
what happens is:
Index - loads a list of boards, with empty boardItems array (I don't want to load all of the data on the index)
Then clicking a link to a specific board, transitions to it, but the data is empty and no requests made to the server.
I tried various ways of reloading it, but all fails...
here is the debug info if it might help:
DEBUG: Ember : 1.5.1 ember.js:3521
DEBUG: Ember Data : 1.0.0-beta.7.f87cba88 ember.js:3521
DEBUG: Handlebars : 1.1.2 ember.js:3521
DEBUG: jQuery : 2.1.0
Thanks!
Finding Records: If you provide a number or string as the second argument to store.find(), Ember Data will attempt to retrieve a record of that with that ID. This will return a promise that fulfills with the requested record:
App.BoardShowRoute = Ember.Route.extend({
model: function(params) {
// Providing both the model name, and board identifier
// Make sure the parameter exists.
// console.debug('My board identifier is', params.board_id');
return this.store.find('board', params.board_id); // => GET /boards/:board_id
}
});

EmberJS: How to load multiple models on the same route?

While I am not new to web development, I am quite new to to client-side MVC frameworks. I did some research and decided to give it a go with EmberJS. I went through the TodoMVC guide and it made sense to me...
I have setup a very basic app; index route, two models and one template. I have a server-side php script running that returns some db rows.
One thing that is very confusing me is how to load multiple models on the same route. I have read some information about using a setupController but I am still unclear. In my template I have two tables that I am trying to load with unrelated db rows. In a more traditional web app I would have just issued to sql statements and looped over them to fill the rows. I am having difficulty translating this concept to EmberJS.
How do I load multiple models of unrelated data on the same route?
I am using the latest Ember and Ember Data libs.
Update
although the first answer gives a method for handling it, the second answer explains when it's appropriate and the different methods for when it isn't appropriate.
BEWARE:
You want to be careful about whether or not returning multiple models in your model hook is appropriate. Ask yourself this simple question:
Does my route load dynamic data based on the url using a slug :id? i.e.
this.resource('foo', {path: ':id'});
If you answered yes
Do not attempt to load multiple models from the model hook in that route!!! The reason lies in the way Ember handles linking to routes. If you provide a model when linking to that route ({{link-to 'foo' model}}, transitionTo('foo', model)) it will skip the model hook and use the supplied model. This is probably problematic since you expected multiple models, but only one model would be delivered. Here's an alternative:
Do it in setupController/afterModel
App.IndexRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON('/books/' + params.id);
},
setupController: function(controller, model){
this._super(controller,model);
controller.set('model2', {bird:'is the word'});
}
});
Example: http://emberjs.jsbin.com/cibujahuju/1/edit
If you need it to block the transition (like the model hook does) return a promise from the afterModel hook. You will need to manually keep track of the results from that hook and hook them up to your controller.
App.IndexRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON('/books/' + params.id);
},
afterModel: function(){
var self = this;
return $.getJSON('/authors').then(function(result){
self.set('authors', result);
});
},
setupController: function(controller, model){
this._super(controller,model);
controller.set('authors', this.get('authors'));
}
});
Example: http://emberjs.jsbin.com/diqotehomu/1/edit
If you answered no
Go ahead, let's return multiple models from the route's model hook:
App.IndexRoute = Ember.Route.extend({
model: function() {
return {
model1: ['red', 'yellow', 'blue'],
model2: ['green', 'purple', 'white']
};
}
});
Example: http://emberjs.jsbin.com/tuvozuwa/1/edit
If it's something that needs to be waited on (such as a call to the server, some sort of promise)
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
model1: promise1,
model2: promise2
});
}
});
Example: http://emberjs.jsbin.com/xucepamezu/1/edit
In the case of Ember Data
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: store.find('dog')
});
}
});
Example: http://emberjs.jsbin.com/pekohijaku/1/edit
If one is a promise, and the other isn't, it's all good, RSVP will gladly just use that value
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: ['pluto', 'mickey']
});
}
});
Example: http://emberjs.jsbin.com/coxexubuwi/1/edit
Mix and match and have fun!
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: Ember.RSVP.Promise.cast(['pluto', 'mickey']),
weather: $.getJSON('weather')
});
},
setupController: function(controller, model){
this._super(controller, model);
controller.set('favoritePuppy', model.dogs[0]);
}
});
Example: http://emberjs.jsbin.com/joraruxuca/1/edit
NOTE: for Ember 3.16+ apps, here is the same code, but with updated syntax / patterns: https://stackoverflow.com/a/62500918/356849
The below is for Ember < 3.16, even though the code would work as 3.16+ as fully backwards compatible, but it's not always fun to write older code.
You can use the Ember.RSVP.hash to load several models:
app/routes/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
people: this.store.findAll('person'),
companies: this.store.findAll('company')
});
},
setupController(controller, model) {
this._super(...arguments);
Ember.set(controller, 'people', model.people);
Ember.set(controller, 'companies', model.companies);
}
});
And in your template you can refer to people and companies to get the loaded data:
app/templates/index.js
<h2>People:</h2>
<ul>
{{#each people as |person|}}
<li>{{person.name}}</li>
{{/each}}
</ul>
<h2>Companies:</h2>
<ul>
{{#each companies as |company|}}
<li>{{company.name}}</li>
{{/each}}
</ul>
This is a Twiddle with this sample: https://ember-twiddle.com/c88ce3440ab6201b8d58
Taking the accepted answer, and updating it for Ember 3.16+
app/routes/index.js
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class IndexRoute extends Route {
#service store;
async model() {
let [people, companies] = await Promise.all([
this.store.findAll('person'),
this.store.findAll('company'),
]);
return { people, companies };
}
}
Note, it's recommended to not use setupController to setup aliases, as it obfuscates where data is coming from and how it flows from route to template.
So in your template, you can do:
<h2>People:</h2>
<ul>
{{#each #model.people as |person|}}
<li>{{person.name}}</li>
{{/each}}
</ul>
<h2>Companies:</h2>
<ul>
{{#each #model.companies as |company|}}
<li>{{company.name}}</li>
{{/each}}
</ul>
I use something like the answer that Marcio provided but it looks something like this:
var products = Ember.$.ajax({
url: api + 'companies/' + id +'/products',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var clients = Ember.$.ajax({
url: api + 'clients',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var updates = Ember.$.ajax({
url: api + 'companies/' + id + '/updates',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var promises = {
products: products,
clients: clients,
updates: updates
};
return Ember.RSVP.hash(promises).then(function(data) {
return data;
});
If you use Ember Data, it gets even simpler for unrelated models:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller,model);
var model2 = DS.PromiseArray.create({
promise: this.store.find('model2')
});
model2.then(function() {
controller.set('model2', model2)
});
}
});
If you only want to retrieve an object's property for model2, use DS.PromiseObject instead of DS.PromiseArray:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller,model);
var model2 = DS.PromiseObject.create({
promise: this.store.find('model2')
});
model2.then(function() {
controller.set('model2', model2.get('value'))
});
}
});
The latest version of JSON-API as implemented in Ember Data v1.13 supports bundling of different resources in the same request very well, if you don't mind modifying your API endpoints.
In my case, I have a session endpoint. The session relates to a user record, and the user record relates to various models that I always want loaded at all times. It's pretty nice for it all to come in with the one request.
One caveat per the spec is that all of the entities you return should be linked somehow to the primary entity being received. I believe that ember-data will only traverse the explicit relationships when normalizing the JSON.
For other cases, I'm now electing to defer loading of additional models until the page is already loaded, i.e. for separate panels of data or whatever, so at least the page is rendered as quickly as possible. Doing this there's some loss/change with the "automatic" error loading state to be considered.

Ember.js - Asyncronous call in model find() method

I have implemented find() and findAll() methods on my Property model. Both methods make asynchronous calls to an API. findAll() is called while connecting the outlets for my home route, and works fine. find() is called by Ember.js while connecting the outlets for my property route. Note that find() is not called when navigating to a property route through actions, but is called when you go directly to the route through the URL.
Here is my router:
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
showProperty: Ember.Route.transitionTo('property'),
home: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('home', App.Property.findAll());
}
}),
property: Ember.Route.extend({
route: '/property/:property_id',
connectOutlets: function(router, property) {
router.get('applicationController').connectOutlet('property', property);
}
}),
})
});
And here are my findAll() and find() methods:
App.Property.reopenClass({
find: function(id) {
var property = {};
$.getJSON('/api/v1/property/' + id, function(data) {
property = App.Property.create(data.property);
});
return property;
},
findAll: function() {
var properties = [];
$.getJSON('/api/v1/properties', function(data) {
data.properties.forEach(function(item) {
properties.pushObject(App.Property.create(item));
});
});
return properties;
}
});
When I go to a route other than index, for example http://app.tld/#/property/1, the route gets rewritten to http://app.tld/#/property/undefined. Nothing is being passed to the content property of the Property controller. How can I make asynchronous calls in the find() method? Unless I am mistaken, asynchronous calls work fine in the findAll() method, which is the source of my confusion.
This question is similar to Deserialize with an async callback, but I'm using the find() method instead of overriding the deserialize() method.
Thanks in advance.
I found that setting the id property explicitly solves this problem. In your case this would look like this.
find: function(id) {
var user = App.User.create();
$.getJSON('/api/v1/property/' + id, function(data) {
user.setProperties(data.user)
});
user.set("id",id); // <-- THIS
return user;
}
Once your user gets its properties set the view updates as normal. Ember just need the id part before in order to update the URL.
Hope this helps :-)
Here's what you want to be doing. I changed the model to User to make things a little clearer.
In the case of find(), you return a blank model instance that gets it's properties filled in when the AJAX request comes back. The nice thing about Ember's data-binding is that you can display this model in a view immediately and the view will update when the AJAX request returns and updates the model instance.
In the case of findAll(), you return a blank array that gets filled in when the AJAX request comes back. In the same way as find(), you can display this list of models (which at first will be blank) in a view and when the AJAX request returns and fills in the array, the view will update.
App.User.reopenClass({
find: function(id) {
var user = App.User.create();
$.getJSON('/api/v1/property/' + id, function(data) {
user.setProperties(data.user)
});
return user;
},
findAll: function() {
var userList = [];
$.getJSON('/api/v1/properties', function(data) {
var users = data.users.map(function(userData) {
return App.User.create(userData);
});
userList.pushObjects(users);
});
return userList;
}
});

Categories