@each not updating computed sum value


#1

In a route with an array model, I need a couple of summary statistics available. These summary statistics need to be updated based on values typed into numeric input fields. I have attempted to implement this by setting these as computed properties using @each in a controller.

The properties (creditTotal and costTotal) compute on load, but fail to update when values are updated through the input fields. Unfortunately, they need to be updating, and I am at a loss how to make this happen.

Admittedly I am not a full time developer, so I am grateful for any assistance and insight you may be able to offer.

Here are the relevant files, starting with the controller.

// ./app/controllers/index.js

import Controller from '@ember/controller';
import { computed } from '@ember/object';

export default Controller.extend({
  creditTotal: computed.sum('model.@each.units', function(){
    return this.get('model').mapBy('creditCost');
  }),
  costTotal: computed.sum('model.@each.units', function(){
    return this.get('model').mapBy('cost');
  })
});

Next, the model being referenced.

// ./app/models/credit-object.js

import DS from 'ember-data';
import { computed } from '@ember/object';

const _creditCost = 0.1;

export default DS.Model.extend({

  name: DS.attr('string'),
  description: DS.attr('string'),
  creditRate: DS.attr('number'),
  unitRate: DS.attr('number'),
  units: DS.attr('number', { defaultValue: 0 }),

  rate: computed('creditRate', 'unitRate', function(){
    return Number(this.get('creditRate')) / Number(this.get('unitRate'));
  }),
  creditCost: computed('rate', 'units', function(){
    return this.get('rate') * this.get('units');
  }),
  cost: computed('creditCost', function(){
    return this.get('creditCost') * _creditCost;
  }),
});

Finally, the template, so it hopefully makes some sense.

<table class="table table-striped table-sm">
  <thead>
  <tr>
    <th scope="col">Name</th>
    <th scope="col">Credit Rate</th>
    <th scope="col">Unit Count</th>
    <th scope="col">Credit Count</th>
    <th scope="col">Cost</th>
  </tr>
  </thead>
  <tbody>
  {{#each model as |creditObject|}}
    <tr>
      <td>{{creditObject.name}}</td>
      <td>{{creditObject.rate}}</td>
      <td>{{input type='number' value=creditObject.units}}</td>
      <td>{{format-floating-point creditObject.creditCost}}</td>
      <td>{{format-currency creditObject.cost}}</td>
    </tr>
  {{/each}}
  <tr class="table-primary">
    <td>Total</td>
    <td></td>
    <td></td>
    <td>{{format-floating-point creditTotal}}</td>
    <td>{{format-currency costTotal}}</td>
  </tr>
  </tbody>
</table>

#2

check if creditTotal & costTotal are computing when used without format helpers. if so, then you have issue in helpers, if not check how you set new model values.


#3

I eventually figured out the solution through a lot of trial and error. While not exactly the most elegant, this is what eventually worked with Ember.js version 2.18.

creditArray: computed('model.@each.creditCost', function(){
  return this.get('model').mapBy('creditCost');
}),
creditTotal: computed.sum('creditArray')

I did stumble across an enhancement request discussing chaining of these types of functions so it could become something like this.

this.get('model').mapBy('creditCost').sum()

Currently this does not work, but I definitely hope it will in the future!


#4

Not sure but perhaps you can use ember-awesome-macros for this.