This question already has an answer here:
I have to access a property that is two level's deep on my controller, but the []
only can access one level deep via the emberjs guide.
model(params) {
params.paramMapping = {
page: "page",
perPage: "per_page",
total_pages: "pages"
};
return this.findPaged('contractf', params);
},
setupController(controller, model) {
model.forEach(function(item) {
item.set('sale_price', item.get('dealers_sched_id.sale_price'));
});
controller.set('content', model);
},
Above, I have my model which is basically fetching all records in a pagination format in the contractf model. Then I set up my controller and loop through all those models and bind a sale_price
property that goes into it's relationship to get the sale_price in the correct model relation.
Now in my template, I have this:
new_suggested_price: Ember.computed('selectedItems', 'selectedItems.[].sale_price', function() {
var ret = 0;
this.get('selectedItems').filterBy('car_used', 'N').forEach(function(contract){
var salePrice = contract.get('sale_price');
if (salePrice) {
ret += (salePrice*100);
}
});
return ret/100; // We *100/100, so we avoid a floating point calc error.
}),
Basically just gives me a number that is easily format-able. As you can see it depends on the selectedItems (which is basically the model, but filters by a property). So I have to go into each model item and find that sale_price
I property I set and if it changes, this computed property will update. Reading Ember's guide, I couldn't do selectedItems.[].dealers_sched_id.sale_price
because it only goes one level deep.
I thought setting a property on setupController would fix that issue, but it doesn't seem to because I'm still getting NaN
as the sale_price
value.
Now if I set a setTimeout function for 500 ms, it populates fine.. How do I get it defined on page load?
Thank you for any help.
in this line here, you are essentially trying to create an alias, which is the right idea, becuase it is a "model-level" concern.. which you are tying to do at the controller level.
You could create a
computed.alias('dealers_sched_id.sale_price')
on your model definition, and avoid all the extra layers of computing properties.Edit: in your case we are dealing with an asynchronous relationship
This means you need to be aware that the value will not always be available to you when the relationship promise is still resolving itself. the reason you are getting a
NaN
is that the belongsTo is still technically "loading"... so any synchronous function you try to perform is likely to fail on you.When you look at both answers provided to you, involving computed properties, understand that both approaches will work... they will compute and recompute when the promises resolve.
somewhere in your code, you might be trying to access the eventual value, before it is actually ready for you... maybe an
alert()
console.log()
or one of Ember's synchronous life-cycle hooks (hint: the setupController)?Another approach, could be to use your route's
model()
hook orafterModel()
to ask for thedealers_sched_id
object before resolving the route... it may be less ideal, but it will ensure you have all the data you need before trying to use the values.Why does this problem exist
Ember API indeed allows you to use only one level of
@each
/[]
in your computed property dependencies.This limitation is likely artificial as using two or more
@each
levels is a huge performance impact on internal observer maintenance.Thus, you have to avoid more than one
@each
/[]
in your CP dependency chains.If you can't have a cake with N candles, have N cakes with one candle each
But sometimes your business dictates you to have to or more levels.
Fear not! This can be achieved with a chain of computed properties, where each property has only one
@each
level.Say, you have Foo, Bar and Baz models and want to depend on
Here's a chain of computed properties you need to create:
barsArrays: computed('foos.@each.bars')
-- mapfoos
bybars
. You'll have an array of arrays of bars.bars: computed('barsArrays.[]')
-- flatten it to receive an array of bars.bazesArrays: computed('bars.@each.bazes')
-- mapbars
bybazes
.bazes: computed('bazesArrays.[]')
-- flattenbazesArrays
.bazesNames: computed('bazes.@each.name')
-- mapbazes
byname
.How to make the chain shorter
Note that you can make this chain shorter (but not necessarily more performant) by relying on the fact that
bar.bazes
is a relationship array that is never replaced with a different array (only its content changes, but the array object stays the same).bazesArrays: computed('foos.@each.bars')
-- mapfoos
bybars
, flatten, then map bybazes
. You'll have an array of arrays of bazes.bazes: computed('bazesArrays.[]')
-- flattenbazesArrays
.bazesNames: computed('bazes.@each.name')
-- mapbazes
byname
.Here's a working demo: http://emberjs.jsbin.com/velayu/4/edit?html,js,output