These are my files:
Models
app/models/basket.js:
export default DS.Model.extend({
name: DS.attr('string'),
house: DS.belongsTo('house', { async: true }),
boxes: DS.hasMany('box', { async: true })
});
app/models/box.js:
export default DS.Model.extend({
qty: DS.attr('number'),
basket: DS.belongsTo('basket'),
cartLines: DS.hasMany('cart-line', { async: true })
});
app/models/cart-line.js:
export default DS.Model.extend({
qty: DS.attr('number'),
box: DS.belongsTo('box'),
product: DS.belongsTo('product')
});
app/models/product.js:
export default DS.Model.extend({
name: DS.attr('string'),
price: DS.attr('number')
});
Routes
app/routes/basket.js:
export default Ember.Route.extend({
model(params) {
return Ember.RSVP.hash({
basket: this.store.findRecord('basket', params.basket_id),
boxes: this.store.findAll('box'),
products: this.store.findAll('product')
});
},
setupController(controller, models) {
controller.setProperties(models);
}
});
Controllers
app/controllers/basket.js:
export default Ember.Controller.extend({
subTotal: Ember.computed('boxes.@each.cartLines', function () {
return this.products.reduce((price, product) => {
var total = price + product.get('price');
return total;
}, 0);
})
});
Questions:
I'm newbie, so I'm studying and makings mistakes. Sorry.
1) Which is the best Ember way to filter relationships when I first enter in route?
For example now I load every box in my app whith boxes: this.store.findAll('box')
. I need a way to not load all the box in my webapp, just the one in basket. I need the "query with filter" directly from a backend?
UPDATED QUESTION
2) Which is the best Ember way for calculate subTotal? Now, with code below, Ember gives me the subTotal but just in console.log(tot)
and after the promises! Why this? How can I wait the promises? I don't understand what to do:
subTotal: Ember.computed('basket.boxes.@each.cartLines', function () {
let count = 0;
console.log('subTotal called: ', count);
// It should be 0 ever
count = count + 1;
return this.get('basket.boxes').then(boxes => {
boxes.forEach(box => {
box.get('cartLines').then(cartLines => {
cartLines.reduce(function (tot, value) {
console.log('tot:', tot + value.get('product.price'));
return tot + value.get('product.price');
}, 0);
});
});
});
});
It gives me in template [object Object] because I'm also using in hbs {{log subTotal}}
and in console it gives me this:
subTotal called: 0
ember.debug.js:10095 Class {__ember1476746185015: "ember802", __ember_meta__: Meta}
subTotal called: 0
ember.debug.js:10095 Class {__ember1476746185015: "ember934", __ember_meta__: Meta}
ember.debug.js:10095 Class {isFulfilled: true, __ember1476746185015: "ember934", __ember_meta__: Meta}
subTotal called: 0
ember.debug.js:10095 Class {__ember1476746185015: "ember1011", __ember_meta__: Meta}
ember.debug.js:10095 Class {isFulfilled: true, __ember1476746185015: "ember1011", __ember_meta__: Meta}
tot: 3.5
tot: 6
tot: 13.5
tot: 21
tot: 24.5
tot: 27
tot: 3.5
tot: 6
tot: 13.5
tot: 21
tot: 24.5
tot: 27
tot: 3.5
tot: 6
tot: 13.5
tot: 21
tot: 24.5
tot: 27
Why it shows three times subTotal called: 0
, no matter if there are zero or one or a thousand products. He always calls three times subTotal called: 0
, why?
Is it good to use computed properties with promises?
3) Am I right with that relationship encapsulation?
UPDATED QUESTION 2:
Now I'm using this code, but without success:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Controller.extend({
totalCount: Ember.computed('basket.boxes.@each.cartLines', function () {
let total = 0;
const promise = this.get('basket.boxes').then(boxes => {
boxes.map(box => {
// const trypromise = boxes.map(box => {
console.log('box:', box);
box.get('cartLines').then(cartLines => {
console.log('cartLines:', cartLines);
const cartLinesPromise = cartLines.map(cartLine => {
console.log('cartLine:', cartLine);
// return cartLine.get('qty');
// return cartLine;
// });
return {
qty: cartLine.get('qty'),
price: cartLine.get('product.price')
};
// return cartLines.map(cartLine => {
// console.log('cartLine:', cartLine);
// return cartLine.get('qty');
// // return {
// // qty: cartLine.get('qty'),
// // price: cartLine.get('product.price')
// // };
// });
})
// });
return Ember.RSVP
.all(cartLinesPromise)
.then(cartLinesPromise => {
console.log('cartLinesPromise:', cartLinesPromise);
// cartLinesPromise.reduce((tot, price) => {
// console.log('tot:', tot);
// console.log('price:', price);
// console.log('tot+price:', tot + price);
// return tot + price, 0;
// });
return total = 10;
// return total;
})
});
});
// return total;
});
return DS.PromiseObject.create({ promise });
})
})
Comments are for many try.
In template I use:
{{log 'HBS totalCount:' totalCount}}
{{log 'HBS totalCount.content:' totalCount.content}}
Total: {{totalCount.content}}
But promise
have null
content.
Where I'm wrong?
Any incorrect return
?
Is this code "promising" correct?
The solution to your question is nicely explained in How to return a promise composed of nested models in EmberJS with EmberData? by @Kingpin2k.
What you want to do is just load a basket and its associated models(box, cat-line and product) rather than loading all boxes, cartLines and products. Also to compute the subTotal, we would need all those dependency promises resolved beforehand. Following the solution given in the post mentioned earlier, your solution would look like:
MODEL: app/models/cart-line.js
ROUTE: app/routes/basket.js
CONTROLLER: app/controllers/basket.js
Lastly, for the issue you were having here:
you would not use the computed construct if following the solution given above, but just wanted to point out solution in similar conditions.
There is nothing bad to being new to technology, especially when your question is well formatted and think through.
1) Which is the best Ember-Data way to filter relationships?
This is complex question with a lot of possible endings.
The easiest thing to do is just ask on that model.
Ask on basket
Given your model you can do:
But this has few limitations and advantages
Edit:
you need to send ids with basket
This means thatbasket
in your payload will have to provide identification for it's boxes. In case of rest api:{basket: {id: 1, boxes: [1,2,3], ...}
. It will then check which ids are not loaded into the store already and ask api here (assuming that box with id 2 is already loaded):/boxes?ids[]=1&ids[]=3
.Ask yourself
query
paramEdit:
if you don't like it you would have to use peekAll and filter to check if you have all of them
You can actually check that with hasMany.Sideload them
Instead of sending two requests to server you can make your api so that it will append boxes into the payload.
Load only basket and let rest to load from template
You can load only bare minimum (like load only basket), let ember continue and render the page. It will see that you are accessing
basket.boxes
property and fetch them. This wont look good on its own and will need some additional work like spinners and so on. But this is one way how to speed up boot and initial render time.2) Which is the best Ember way for calculate subTotal
You want to calculate sum of something that is three levels deep into async relationships, that's not going to be easy. First of I would suggest putting totalPrice computed property into basket model itself. Computed properties are lazily evaluated so there is no performance degradation and this is something that model should be able to provide.
Here is little snippet:
You would need to write something like this for each level or give up some of the async relations. The problem with your computed property is that
boxes.@each.cartLines
wont listen on everything that can change overall price (for example change of price of product itself). So it won't reflect and update on all possible changes.I would sagest to give up some async relations. For example request on
/baskets/2
could sideload all of its boxes, cartLines and maybe even products. If your api doesn't support sideloading, you can fake it by loading everything in route (you would have to use second example - you are not allowed to access boxes before they are in the store in case ofasync: false
). That would lead to much simpler computed properties to calculate total price and in case of sideloading also reduce stress on server and clients confections.Update and overall after thoughts
I don't think that doing all sums in one function is viable, doable or sane. You will end up in callback hell or some other kind of hell. Moreover this is not going to be performance bottleneck.
I made jsfiddle it is basicaly more fleshed out version of snippet above. Note that it will properly wait and propagate price which is two promises deep and also should update when something changes (also I didn't test that).