Ember 2, filter relationship models (hasMany, owned by To) and compute the calculated property based on the relationship

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 }) }); 

application / models / box.js:

 export default DS.Model.extend({ qty: DS.attr('number'), basket: DS.belongsTo('basket'), cartLines: DS.hasMany('cart-line', { async: true }) }); 

application / models / basket-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 am a newbie, so I study errors and errors. Unfortunately.

1) What is the best Ember method for filtering relationships when you first enter a route? For example, now I load each box into my application with boxes: this.store.findAll('box') . I need a way to not load all the boxes in my webapp, just in the trash. Do I need a "filter request" directly from the backend?

UPDATE QUESTION 2) What is the best way to calculate subTotal level? Now, with the code below, Ember gives me subTotal, but only in console.log(tot) and after promises! Why is this? How can I wait for promises? I do not 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 the [object Object] template, because I also use hbs {{log subTotal}} , and in the console it gives me the following:

 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 , regardless of whether there are zero or one or a thousand products. It always calls three times subTotal called: 0 , why ?

Is it good to use computed properties with promises?

3) Am I correct with this encapsulation of relationships?

QUESTION 2 UPDATED :

Now I use this code, but to no avail:

 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 for many attempts.

In the template, I use:

 {{log 'HBS totalCount:' totalCount}} {{log 'HBS totalCount.content:' totalCount.content}} Total: {{totalCount.content}} 

But promise has null content.

Where am I wrong?

Any wrong return ?

Is this code โ€œpromisingโ€ correct?

+7
javascript ember-data relationship
source share
2 answers

There is nothing wrong with being new to technology, especially when your question is well formatted and thought out.

1) What is the best Ember-Data way to filter relationships?

This is a tricky question with many possible endings.

The easiest way is to simply ask for this model.

Ask in basket

Given your model, you can:

 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 it has few limitations and advantages.

  • you need to send identifiers with a basket
  • you need to enable coalesceFindRequests to make it normal.
  • it will only load containers that are not in the store

Edit: you need to send ids with basket This means that the basket in your payload will need to provide an identity for it. In case of rest api: {basket: {id: 1, boxes: [1,2,3], ...} . Then it checks which identifiers are not already loaded into the repository, and ask api here (assuming that the field with id 2 is already loaded): /boxes?ids[]=1&ids[]=3 .

Ask yourself

 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}), }); }, 
  • On the other hand, this approach will send a request to the basket only if the basket is not already in the store (as before) but always request fields (if you do not like it, you will need to use peekAll and a filter to check if there is whether you have all of them or smt).
  • Itโ€™s good to think that requests will be parallel non-serial in order to speed up the process.
  • The cart also should not send the identifiers of its boxes.
  • You can perform server-side filtering by changing query param

Edit: if you don't like it you would have to use peekAll and filter to check if you have all of them In fact, you can check this with hasMany .

Save them

Instead of sending two requests to the server, you can make your api so that it adds boxes to the payload.

Download only the cart and let rest for loading from the template

You can load only the bare minimum (for example, the download cart), let ember continue and display the page. He will see that you access basket.boxes and get them. It will not look on its own and will require additional work, like spinners and so on. But this is one way to speed up loading and initial rendering time.

2) What is the best Ember method for calculating subTotal

You want to calculate the sum of something that is at three levels in an asynchronous relationship, which will not be easy. First, I would suggest including the computed property totalPrice in the cart model. Computational properties are lazily evaluated, so there is no performance degradation, and this is what the model should be able to provide.

Here is a small 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 will need to write something similar for each level or abandon some asynchronous relationships. The problem with your computed property is that boxes.@each.cartLines will not listen to anything that can change the overall price (for example, changing the price of the product itself). Therefore, it will not reflect and update all possible changes.

I would like to give up some asynchronous relationships. For example, a request to /baskets/2 could bypass all its fields, cartLines, and possibly even products. If your api does not support side loading, you can fake it by loading everything into the route (you will need to use the second example - you are not allowed to open windows before they are in the store in case of async: false ). This would lead to much simpler computational properties for calculating the total price, and in the case of lateral loading would also reduce the load on the confectionery products on the server and clients.

 // 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')); }), 

Refresh and generally after thoughts

I do not think that executing all sums in one function is viable, executable, or normal. You will find yourself in a hellish way back or some other hell. In addition, this will not be a performance bottleneck.

I made jsfiddle , basically the fragment version above is described in more detail. Please note that it will wait and distribute the price correctly, which is two promises, and should also be updated when something changes (also I have not tested this).

+2
source share

The solution to your question is well explained in How to return a promise consisting of nested models in EmberJS with EmberData? by @ Kingpin2k.

What you want to do is just download the cart and its associated models (box, cat-line and product), and not download all the boxes, cartLines and products. Also, to calculate subTotal, we need all of these promises dependencies to be resolved in advance. Following the solution above, your solution will look like this:

MODEL: application / models / cart-line.js

 export default DS.Model.extend({ qty: DS.attr('number'), box: DS.belongsTo('box'), product: DS.belongsTo('product', { async: true })//assuming you are not side-loading these }); 

ROUTE: app / routes / basket.js

 export default Ember.Route.extend({ model(params) { return this.store.findRecord('basket', params.basket_id).then((basket)=> { return basket.get('boxes').then((boxes)=> { let cartLinesPromises = boxes.map(function (box) { return box.get('cartLines'); }); return Ember.RSVP.allSettled(cartLinesPromises).then((array)=> { let productPromises = array.map(function (item) { return (item.value).get('product'); }); return Ember.RSVP.allSettled(productPromises); }); }); }); } }); 

CONTROLLER: application / controllers / basket.js

 subTotal: computed(' model.boxes.@each.cartLines ', function () { //you dont need to use DS.PromiseArray because the promises all have been already resolved in the route model hook let total = 0; this.get('model.boxes').forEach((box)=> { box.get('cartLines').forEach((cartLine)=> { total += cartLine.get('product.price'); }); }); return total; }) 

Finally, for the question you had here:

 subTotal: computed(' boxes.@each.cartLines ', function() { return DS.PromiseArray.create({ //"this" here is DS.PromiseArray object and not your controller instance promise: this.get('boxes').then(boxes => { return boxes.filter(i => i.get('cart-line')); }) }); }) 

you would not use a calculated construct if, after the solution given above, but just wanted to specify the solution in similar conditions.

 subTotal: computed(' boxes.@each.cartLines ', function() { let controllerInstance = this; return DS.PromiseArray.create({ promise: controllerInstance.get('boxes').then(boxes => { return boxes.filter(i => i.get('cart-line')); }) }); }) 
+1
source share

All Articles