Looping over an Ember.js promise - javascript

I am trying to loop over what I believe to be an Ember promise, but all I can seem to get returned is an object, when it should be an array.
jsbin: http://emberjs.jsbin.com/qakine/1/edit
If I just loop over the items in the template then I have no issues, but I want to be able to interact with the array items in my controller. Any help appreciated.

Quite a few things here, let me see if I can remember them all:
When you have a route under a resource, the routes and controllers of that route should take their parent's name.
App.Router.map(function() {
this.resource("parent", function(){
this.route("child");
});
});
App.ParentChildRoute...
App.ParentChildController...
Handlebars can't access normal functions from your controller, this should be converted into a computed property.
App.ParentChildController = Ember.ObjectController.extend({
middleNames: function(){
...
}.property('middles.[]')
});
naming is case sensitive
{{#each name in middlenames}}
should be
{{#each name in middleNames}}
Example: http://emberjs.jsbin.com/cowibi/1/edit

I created an new example based on yours to make things clear. You can check it out here:
http://emberjs.jsbin.com/hokabe/4/edit
For Ember route's model hook, if the return value is a promise, the route will wait for the promise resolved and pass the resolved value to controller's model property.
And a App.Parent instance's middles property returns a promise (actually it's a DS.PromiseArray instance) which will resolve a middles array (actually it's a DS.ManyArray instance).
so for getting children you can simply do this:
App.ParentMiddlesRoute = Ember.Route.extend({
model: function() {
return this.modelFor('parent').get('middles');
}
});
Note that modelFor argument is a route name but not a model name. It means "Get the model from given route".

Related

then() returning a null value when it should be pending

Ember 2.17
I am calling an helper from my template :
{{#each invoice.invoiceLines as |line| }}
{{pricings/full-pricing line.pricing}}
{{/each}}
invoice,invoiceLine, as well as pricing are ember models.
Here is how invoice is created in model () :
model(params) {
let invoice= this.store.findRecord('invoice',params.invoice_id)
return Ember.RSVP.hash({
invoice: invoice,
allShares: invoice.then((i)=>{return i.allShares()}),
detailShares: invoice.then((i)=>{return i.detailShares()})
});
}
The goal of the helper is to take pricing, extract numbers (everything is in the model, no more relations) and return a string formatting the initial price and the subscription price.
The helper is as following :
import { helper } from '#ember/component/helper';
export function pricingsFullPricing([pricing]) {
return pricing.then(
p=>{
debugger
},p=>{
}
)
}
export default helper(pricingsFullPricing);
When I run the page, debugger is called twice (the template loop run once).
First time p is null, the second time it is a pricing.
Isn't then supposed to prevent that? Why does it behave like that?
Your route is wrong, routes are promise aware (that's what hash is for), it should be:
model(params) {
return Ember.RSVP.hash({
invoice: this.store.findRecord('invoice',params.invoice_id)
//allShares: invoice.then((i)=>{return i.allShares()}),
//detailShares: invoice.then((i)=>{return i.detailShares()})
});
}
Then your handlebars is just:
{{#each model.invoice.invoiceLines as |line| }}
{{line}}
{{/each}}
You also shouldn't call methods like you are on a model. It's not really clear what allShares(), etc does but these should (probably) be computed in the controller. Something along the lines of:
import { computed } from '#ember/object';
export default Controller.extend({
allShares:computed('model.invoice', function(){
return this.get('model.invoice').allShares();
});
});
Though this doesn't seem ideal. Like I said, it's hard to be explicit as it's not clear what your trying to do here. It'd probably make more sense if your extracted these methods into a service.
You then don't need the helper at all. This appears to be just trying to work around promises.
It makes life a lot easier if you try and load all server side data in the route before load.
First rule of helpers
Each time the input to a helper changes, the compute function will be called again.
Second, there's nothing about helpers that will make this block subsequent calls because you are returning a promise.
export function pricingsFullPricing([pricing]) {
return pricing.then(
p=>{
debugger
},p=>{
}
)
}
You've created a simple helper here that will use the promise itself as the value. Look at ember-promise-helpers/await to see how a class based helper is used to manually set the value that's displayed in the template.
Now, if you're wondering why the recomputation is happening, I'm going to have to speculate based off the knowledge I have of Ember data just from being part of the Ember community (I've never actually used Ember Data). You know line.pricing is a promise? I can then assume your using some sort of relationship, which will most likely have to be loaded via an ajax call (hence the promise). But these relationships in Ember data, iirc, use this PromiseProxyMixin that allow them to behave simultaneously like a promise or like an object (depending on whether the data is in the store already or not). This is what allows you to reference the promise in your template without then
See this article for a better understanding of what I mean

Ember.js dynamic model requests

I am trying to make a request from the store for a model based on the result of a previous model request. Please find the code below:
For the route:
model(params) {
var id = params.framework_id;
var main = this;
return Ember.RSVP.hash({
question: this.store.query('question', {orderBy: 'framework', equalTo: id}),
framework: this.store.find('frameworks', id),
frameworks: this.store.findAll('frameworks')
})
}
Then in the route there is a setupController:
setupController: function(controller, model) {
this._super(controller, model);
var main = this;
...
controller.set("frameworkName", model.framework.get('name'));
var includedFramework = model.framework.get('includedFramework');
var includedFrameworkModel = this.store.find('frameworks', includedFramework);
Ember.Logger.info(model.framework)
Ember.Logger.info(includedFrameworkModel);
if (model.framework.get('includedFramework') != undefined) {
var linked = main.store.find("frameworks", model.framework.get('includedFramework'));
controller.set("linkedFramework", {id: linked.get('id'), name: linked.get('name')});
}
}
In the controller setup, using model.framework.get('name') works without a problem. mode.framework.get('includedFramework') works fine and returns an ID to another framework that is stored in the framework model. I then intend to pull the "included framework" in the store with another store.find request. Unfortunately this second store.find doesn't return the model record in the same way as the first. Here is an inspector view of what each request returns:
model.framework -
includedFrameworkModel -
Any help is greatly appreciated!
Well, every call to ember-data that returns something that may require a request to the server, like find(), findAll(), query(), save(), and async relationships returns a PromiseObject or a PromiseArray.
It works the same way for objects and arrays, so just lets describe how arrays work.
A PromiseArray is both, a Promise and a ArrayProxy.
This is very useful because you can work with it in both ways, depending on your situation.
Because the request to the server may take some time, the resulting ArrayProxy part is often empty, and will be populated with data later. This is very useful because your handlebars template and computed properties will update when the ArrayProxy changes.
The Promise part of the PromiseArray will resolve as soon the data are received from the server, with an actual array, that also may update later when you do further changes on your data.
The ember router however will wait for the promise to be resolved before it loads the route, which allows you to specify a loading substrate.
This is why model.framework is different in setupController. It's not the result of the .find() but the result of the resolved promise. Thats basically what Ember.RSVP.hash does for you.
Generally I recommend you two things.
Don't store a model id on a model, but use a relationship.
Don't call the store in .setupController(). Do all your requests in the .model() hook and use the promise chain to do so.
Maybe take this as a inspiration:
return Ember.RSVP.hash({
question: this.store.query('question', {orderBy: 'framework', equalTo: id}),
framework: this.store.find('frameworks', id),
frameworks: this.store.findAll('frameworks')
}).then(({question, framework, frameworks}) => {
let includedFramework = framework.get('includedFramework');
let includedFrameworkModel = this.store.find('frameworks', includedFramework);
return {question, framework, frameworks, includedFrameworkModel};
});
var includedFrameworkModel = this.store.find('frameworks', includedFramework);
The store.find() method will return the promise, the object was not resolved when you print in log.
Changed your script to something like this.
this.store.find('frameworks', includedFramework).then(function(includedFrameworkModel) {
Ember.Logger.info(includedFrameworkModel);
});

Model object not preserved when using hash for model in ember route

EDIT: I've set up an actual repro of the issue on JSBIN
Been trying to resolve this for a while now and I'm clearly not understanding how the relationship between model and setupController works. I have a model which is returning a hash; the result of two find calls:
model(params) {
return Ember.RSVP.hash({
course: this.store.find('course', params.course_id),
topics: this.store.find('topic', { course_id: params.course_id })
});
},
The first time setupController gets called, the value of model if as expected, a hash like { course: <Class>, topics: <Class> }. Awesome, that's what I want.
However, the next time setupController gets called (for example, transition to another route and then press the back button in the browser), the model is now just the course <Class>:
setupController(controller, model) {
// when first called model will be { course: <Class>, topics: <Class> }
// next time entered, model will just be <Class> (just the value of "course" )
// why is the model object not preserved?
controller.set('model', model.course);
controller.set('topics', model.topics);
}}
If I just make model() return a single resource, it's the same every time:
model(params) { return this.store.find('course', params.course_id); }
// now `model` will always be "course" in setupController
Why is the original model not preserved when using a hash result? Am I doing something wrong?
You're sending the model color when you're linking here:
{{#link-to 'color' color}}{{color.name}}{{/link-to}}
Because of that, the model hooks aren't run. If you change that to color.id, it'll work.
It's mentioned here.
In the above example, the model hook for PhotoRoute will run with
params.photo_id = 5. The model hook for CommentRoute won't run since
you supplied a model object for the comment segment. The comment's id
will populate the url according to CommentRoute's serialize hook.
Looking at it, the original model will not be preserved because on setupController, you are calling controller.set('model', model.course). When it first loads, its called the model(params {} function appropriately, but on back button transitions and certain {{link-to}} calls, that isn't always the case.
In your setupController, try changing it to controller.set('course', model.course);, that way you aren't overwriting your model on execution as well and it will always be able to find it.

How to deal with async properties in Ember when I need blocking calls?

We were using a model with a hasMany with embedded children. This was fine so that whenever I called model.get('children') everything just worked.
We've now changed that children property to async:true, and I can't seem to find proper documentation on how you should handle this.
Let me give you an example. I'll use simplified json to represent my Ember setup, just for the sake of simplicity.
Say I have a Model like this:
model:{
hasMany: {children: {async: true} },
isActive: boolean
}
Say I have a Template like this:
{{#if lastChildIsActive}}
<p>I'm the last one!</p>
{{/if}}
And I have a Controller:
controller:{
lastChildIsActive: function(){
return this.get('model').get('children').get('lastObject').get('isActive')
}
}
Ok, so with this setup when async: false was being used, everything just worked.
But now, with async true, that call in the controller for .get('children') SOMETIMES just doesn't return anything, because it's async I guess.
Now I could use promises, and refactor my controller to this:
controller:{
lastChildIsActive: function(){
this.get('model').get('children').then(function(children){
return children.get('lastObject').get('isActive');
});
}
}
The problem with the second refactor is, I'm not longer returning the isActive value, I'm now returning the promise object.
But the template doesn't want a promise, it needs the returning value.
SO, how can I make sure the async has loaded, while being able to return the actual result of the call instead of the promise?
By using a view and didInsertElement, you can get the children of the parent and then update the attribute on the controller, which will appear in your template.
App.IndexView = Ember.View.extend({
didInsertElement: function() {
controller = this.get('controller');
controller.get('model').get('children').then(function(children){
active = children.get('lastObject').get('active');
controller.set('lastChildIsActive', active);
});
}
});
See this fiddle: http://jsfiddle.net/f9DAc/2/
You have to use observer to listen for the changes in children. Refer the change I have made in lastChildIsActive method below. As children is an array type, I am listening for "children.#each". Whenever, there is a change in "childern", lastChildIsActive will be updated automatically.
controller:{
lastChildIsActive: function(){
return this.get('model').get('children').get('lastObject').get('isActive')
}.property('children.#each')
}

EmberJS transitionTo firstObject for async hasMany

I want to redirect users to the route for the firstObject of a collection when they arrive at the containing object.
A container hasMany items, currently in the ContainerRoute.afterModel I am doing:
this.transitionTo('container.item.index', model, model.get('items').get('firstObject'));
Which is fine when the items are not async.
The problem is the firstObject isn't a promise so the route/transition doesn't pause.
Here is the model hook:
return this.get('store').find('container', params.containerId);
What's the cleanest approach? The options I see are:
change the model hook to populate the
build and pass a promise as the parameter to transitionTo
transitionTo the ID of the object instead of the object (writing a solution for this now)
transitionTo can take an object (actual or promise), or an ID.
I opted to just send the ID in the ContainerRoute:
this.transitionTo('container.item.index', model, model.get('firstItemId'));
You can get the ID without inflating the object by accessing:
this.get('data.submissions.firstObject.id');
Which was borrowed from a github issue about avoiding loadMany
The hasMany property is a promise like object, so you can use the then method, to know when it't is resolved:
model.get('items').then(function(items) {
this.transitionTo('container.item.index', model, items.get('firstObject'));
});

Categories

Resources