7

这些是我的文件:

楷模

应用程序/模型/basket.js:

export default DS.Model.extend({
  name: DS.attr('string'),
  house: DS.belongsTo('house', { async: true }),
  boxes: DS.hasMany('box', { async: true })
});

应用程序/模型/box.js:

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

应用程序/模型/cart-line.js:

export default DS.Model.extend({
  qty: DS.attr('number'),
  box: DS.belongsTo('box'),
  product: DS.belongsTo('product')
});

应用程序/模型/product.js:

export default DS.Model.extend({
  name: DS.attr('string'),
  price: DS.attr('number')
});

路线

应用程序/路线/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);
    }
});

控制器

应用程序/控制器/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')。我需要一种方法来不加载我的 web 应用程序中的所有框,只加载篮子中的一个。我需要直接从后端“使用过滤器查询”吗?

更新的问题 2) 哪个是计算 subTotal 的最佳 Ember 方法?现在,使用下面的代码,Ember 给了我 subTotal,但只是在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}}

promise要有null内容。

我哪里错了?

有什么不正确的return吗?

这段代码“有希望”正确吗?

4

2 回答 2

1

成为技术新手并没有什么不好,尤其是当您的问题格式正确并经过深思熟虑时。

1) 过滤关系的最佳 Ember-Data 方法是什么?

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

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

在篮子上问

给定您的模型,您可以执行以下操作:

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 尚未加载到 store 中,并在此处询问 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)。
  • 好的想法是请求将是并行的而不是串行的,因此它可能会加快速度。
  • Basket 也不必发送其盒子的 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) 这是计算 subTotal 的最佳 Ember 方法

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

这是一个小片段:

// 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不会监听所有可以改变整体价格的东西(例如产品本身价格的变化)。所以它不会反映和更新所有可能的变化。

我会放弃一些异步关系。例如 request on/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,它基本上是上面代码片段的更加充实的版本。请注意,它会正确等待并传播价格,这是两个深度的承诺,并且还应该在发生变化时更新(我也没有测试过)。

于 2016-10-14T01:02:09.710 回答
0

如何使用 EmberData 在 EmberJS 中返回由嵌套模型组成的承诺很好地解释了您的问题的解决方案?通过@Kingpin2k。

您要做的只是加载一个篮子及其相关模型(盒子、cat-line 和产品),而不是加载所有盒子、cartLines 和产品。同样要计算 subTotal,我们需要预先解决所有这些依赖承诺。按照前面提到的帖子中给出的解决方案,您的解决方案将如下所示:

型号: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'));
    })
  });
})
于 2016-10-14T00:56:22.610 回答