Ember 2、过滤关系模型(hasMany, belongsTo)并根据关系计算属性

Ember 2, filter relationship models (hasMany, belongsTo) and calculate computed property based on relationships

这些是我的文件:

型号

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

路线

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

控制器

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

问题:

我是新手,所以我正在学习和犯错。对不起。

1) 当我第一次进入路线时,Ember 过滤关系 的最佳方法是什么? 例如,现在我用 boxes: this.store.findAll('box') 加载我应用程序中的每个框。我需要一种方法来不加载我的 webapp 中的所有框,只加载篮子中的框。我需要直接来自后端的 "query with filter"?

更新问题 2) 计算小计的最佳 Ember 方法是什么? 现在,使用下面的代码,Ember 给了我小计,但只是在 console.log(tot) 和承诺之后!为什么这个?我如何等待承诺?我不知道该怎么办:

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

它在模板 [object Object] 中给了我,因为我也在 hbs {{log subTotal}} 中使用,在控制台中它给了我这个:

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

为什么显示3次subTotal called: 0,不管是零个还是一个还是一千个产品。他总是叫三声subTotal called: 0,为什么?

在 promise 中使用计算属性好吗?

3) 我对这种关系封装是否正确?

更新问题 2:

现在我正在使用这段代码,但没有成功:

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

})

评论是为了多次尝试。

在我使用的模板中:

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

但是promisenull内容。

我哪里错了?

有什么不正确的吗return

这个代码 "promising" 正确吗?

@Kingpin2k 在 How to return a promise composed of nested models in EmberJS with EmberData? 中很好地解释了您问题的解决方案。

您要做的只是加载篮子及其相关模型(盒子、猫线和产品),而不是加载所有盒子、购物车线和产品。此外,为了计算小计,我们需要事先解决所有这些依赖承诺。按照前面提到的 post 中给出的解决方案,您的解决方案将如下所示:

型号:app/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
});

路线: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);
                });
            });
        });
    }
});

控制器:app/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's model hook
    let total = 0;
    this.get('model.boxes').forEach((box)=> {
        box.get('cartLines').forEach((cartLine)=> {
            total += cartLine.get('product.price');
        });
    });
    return total;
})

最后,对于您在这里遇到的问题:

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

如果遵循上面给出的解决方案,您将不会使用计算构造,而只是想指出类似条件下的解决方案。

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) Ember-过滤关系的最佳数据方式是什么?

这是一个复杂的问题,有很多可能的结局。

最简单的方法就是询问该型号。

询问篮子

根据您的模型,您可以执行以下操作:

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

但这有一些局限性和优势

  • 您需要随购物篮一起发送 ID
  • 你必须启用 coalesceFindRequests 才能使其正常
  • 它将只加载商店中没有的盒子

编辑: you need to send ids with basket 这意味着您的有效负载中的 basket 必须为其盒子提供标识。在休息的情况下api:{basket: {id: 1, boxes: [1,2,3], ...}。然后它将检查哪些 id 尚未加载到商店中,并在此处询问 api(假设 id 为 2 的框已加载):/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}),
  });
},
  • 另一方面,仅当购物篮尚未入库时,此方法才会发送购物篮请求(与之前相同) 但始终查询框(如果您不喜欢它,则必须使用 peekAll 和过滤器来检查您是否拥有所有框或类似的 smt)。
  • 好的想法是请求将是并行的而不是串行的,因此它可能会加快速度。
  • 篮子也不必发送其盒子的 ID。
  • 您可以通过更改 query 参数
  • 来进行服务器端过滤

编辑: if you don't like it you would have to use peekAll and filter to check if you have all of them 您实际上可以使用 hasMany.

检查

旁加载它们

您可以创建 api 而不是向服务器发送两个请求,这样它会将框附加到有效负载中。

只加载篮子,其余的从模板加载

您只能加载最低限度(如仅加载篮子),让 ember 继续并呈现页面。它会看到你正在访问 basket.boxes 属性 并获取它们。这本身看起来不太好,需要一些额外的工作,如微调器等。 但这是加快启动和初始渲染时间的一种方法。

2) Ember 计算小计

的最佳方法是什么

您想计算​​深入异步关系的三个级别的总和,这并不容易。 首先,我建议将 totalPrice computed 属性 放入篮子模型本身。计算属性 被延迟评估,因此没有性能下降,这是模型应该能够提供的东西。

这是小片段:

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

您需要为每个级别编写类似的内容或放弃一些异步关系。 您计算出的 属性 的问题是 boxes.@each.cartLines 不会监听所有可能改变整体价格的事情(例如产品本身价格的变化)。因此它不会反映和更新所有可能的更改。

我想放弃一些异步关系。例如,对 /baskets/2 的请求可以旁加载其所有框、cartLines 甚至产品。 如果您的 api 不支持侧载,您可以通过加载路线中的所有内容来伪造它(您将不得不使用第二个示例 - 如果 async: false). 这将导致更简单的计算属性来计算总价,并且在侧载的情况下还可以减少服务器和客户端糖果的压力。

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

更新后的整体思考

我不认为在一个函数中完成所有求和是可行的、可行的或理智的。你最终会陷入回调地狱或其他某种地狱。而且这不会成为性能瓶颈。

我制作了 jsfiddle 它基本上是上面代码片段的更充实版本。请注意,它会正确地等待并传播价格,这是两个承诺的深度,并且在发生变化时也应该更新(我也没有测试)。