My server returns a list of daily updates and I would like to create a running total from those updates to display on my page. At the moment I have that logic in the model hook on my route…
async model() {
return this.store.findAll('update').then(function(results) {
let runningTotal = 0;
results.sortBy('date').forEach((result, i) => {
runningTotal += result.total;
result.runningTotal = runningTotal;
});
return results;
});
This definitely feels like the wrong place to be doing this. I’m returning to Ember after a few years. Way back when I would have done this in a controller but now I can’t figure out a better way to approach this. Can anyone help?
Thanks
[Note: i’m going to assume you’re using Octane, if not these look a bit different but essentially would function the exact same way]
I think typically you’d want to have a “dumber” model hook:
model() {
return this.store.findAll('update');
}
Then in your controller (or a component further down in the template hierarchy) do something like this:
@readOnly('model.length') runningTotal;
sortDefinition = [date'];
@sort('model', 'sortDefinition') sortedResults;
And then reference those things in your template like this:
{{this.runningTotal}}
{{#each this.sortedResults as |result|}}
...
{{/each}}
I think you have misread my code a little bit. The code you give here gives a record count, in my code each record has a field called runningTotal
which is the sum of the total
attribute of the current record and all records which preceded it but not the records that follow.
Ah you’re right my bad. So there are a couple ways you could do this… your approach isn’t wrong per se, but it’s a little unusual to modify ember data records like that with computed state that won’t (I assume) be persisted to the server.
In most scenarios like this I’d think you’d want to use the backend as the source of truth for metrics like running totals. However if you’d rather not do that or don’t have control over the backend, or this is client-specific somehow, you could put this state in a few other places.
One option would be to add a computed property on the model, which would have to grab any records that came before it (either from the store directly or from an intermediary service) to calculate the running total. Another option, and probably what I would try, is moving this state to a service that keeps a findAll “live array” of update records, and has a computed property that returns an object of running totals keyed by “update” id. So every time the computed recalculates (basically if a new record is added) it updates the cached running total map. Then you could inject that service wherever you need to in order to get a running total for a given update record (a CP on the running total model, or a template helper, etc).
Anyway that’s just a couple ideas. Again I don’t think what you’re doing here is necessarily wrong though, and in some ways it’s cleaner and easier, it just may present issues if you need this to recompute automatically, etc without doing a route model refresh.
Unfortunately the back end isn’t within my scope of influence for this so that’s not an option. Your reply confirms what I hoped was not the case: that there is no obvious, Emberish way to achieve this.
Another option that popped into my head was to move this logic deeper into the adapter, so that by the time the data arrives at the route it already has this field filled in. What do you think of that?
Adapter or serializer could work I think but the problem there is that you have to be a little more careful about the ordering of the records to ensure things don’t get miscalculated. I think the adapter/serializer would still need access to all the records, unless you could guarantee that they’ll hit the adapter and/or serializer in the right order…
I still think a service fits this use case pretty well. Especially if it’s just observing the results of peekAll('update')
and recalculating the totals on change. It may not be quite as elegant as you were hoping but It’s nice and isolated that way, isn’t couple to any one route/component/etc, can be injected anywhere, and won’t have any issues in terms of integrity (the totals can be calculated for whatever is in the store, anytime that changes). You could also probably make some optimizations if you needed but I wouldn’t anticipate performance issues unless you had many thousands of update records in which case you’d probably run into Ember Data performance issues anyway.
I think you’re probably right. Thanks very much for the advice.