【问题标题】:Ember 2, filter relationship models (hasMany, belongsTo) and calculate computed property based on relationshipsEmber 2、过滤关系模型(hasMany、belongsTo)并根据关系计算计算属性
【发布时间】:2023-04-09 10:42:01
【问题描述】:

这些是我的文件:

模型

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

更新问题 2) 计算 subTotal 的最佳 Ember 方法是什么? 现在,通过下面的代码,Ember 给了我 subTotal,但只是在 console.log(tot) 和 promise 之后!为什么这个?我怎样才能等待承诺?我不明白该怎么做:

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

为什么它显示三次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

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

【问题讨论】:

    标签: javascript ember.js ember-data relationship ember-controllers


    【解决方案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 必须为其盒子提供标识。如果是rest 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) Ember 计算 subTotal 的最佳方法是什么

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

    这是小sn-p:

    // 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,它基本上是上面sn-p 的更加充实的版本。请注意,它会正确等待并传播价格,这是两个深度的承诺,并且还应该在发生变化时更新(我也没有测试过)。

    【讨论】:

    • 你能更好地解释一下吗:1) you need to send ids with basket you have to enable coalesceFindRequests to make it sane; 2) if you don't like it you would have to use peekAll and filter to check if you have all of them or smt like that。我该如何检查?谢谢。
    • 哪个更快,这个:prices => prices.reduce((carry, price) => carry + price, 0 或者这个:this.get('model.boxes').forEach((box)=> { box.get('cartLines').forEach((cartLine)=> { total += cartLine.get('product.price'); }); });
    • 50 有答案吗?
    • 今天晚些时候我会查看这个答案
    • 我进行了更新。总结一下:我会把它分开,一步一步做。
    【解决方案2】:

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

    您要做的只是加载一个购物篮及其相关模型(盒子、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'));
        })
      });
    })
    

    【讨论】:

    • 这就是为什么我喜欢 ember 而不是 angular。在不到一小时的时间内提出了很好的问题和两个不同、完整和复杂的解决方案。
    • 如何处理模板中返回的模型?现在我收到了这个:{{log model}}:undefined
    • 我更正了自己。 let productPromises = array.map(function (item) { return (item.value).get('product'); }); 似乎是错误的,因为我得到了 productPromises: Array[1] 0: undefined length: 1。为什么(item.value)
    • 哪个更快,这个:prices => prices.reduce((carry, price) => carry + price, 0 或这个:this.get('model.boxes').forEach((box)=> { box.get('cartLines').forEach((cartLine)=> { total += cartLine.get('product.price'); }); });
    • 最后一件事:使用您的 subTotal 代码,我第一次看到该页面时(所有承诺尚未解决)我得到“NaN”而不是总数。如何解决这个问题?也许用@Keo 的 subTotal 方法回答?
    猜你喜欢
    • 1970-01-01
    • 2014-12-21
    • 2016-01-12
    • 2017-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-24
    • 2014-07-14
    相关资源
    最近更新 更多