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?
There is nothing bad to being new to technology, especially when your question is well formatted and think through.
This is complex question with a lot of possible endings.
The easiest thing to do is just ask on that model.
Given your model you can do:
model(params) {
// we will return basket but make boxes ready
return this.get('store').find('basket', params.basket_id).then(basket => {
return basket.get('boxes').then(() => basket);
});
}
But this has few limitations and advantages
Edit:
you need to send ids with basket
This means that basket
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
.
model(params) {
const store = this.get('store');
const basket = params.basket_id;
return RSVP.hash({
model: store.find('basket', basket),
boxes: store.query('box', {basket}),
});
},
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.
Instead of sending two requests to server you can make your api so that it will append boxes into the payload.
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.
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:
// basket.js
const {RSVP, computed} = Ember;
price: computed('boxes.@each.price', function() {
const promise = this.get('boxes').then(boxes => {
// Assuming box.get('price') is computed property like this
// and returns promise because box must wait for cart lines to resolve.
const prices = boxes.map(box => box.get('price'));
return RSVP
.all(prices)
.then(prices => prices.reduce((carry, price) => carry + price, 0));
});
return PromiseObject.create({promise});
}),
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 of async: 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.
// basket.js
const {computed} = Ember;
boxes: DS.hasMany('box', {async: false}),
price: computed('boxes.@each.price', function() {
return this.get('boxes').reduce(box => box.get('price'));
}),
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).