0

I have a filtered list (which is filtered by time - so in a specific timeframe) and over these item I am iterating with ng-repeat. These items have a name and a price. So if I am iterating over them I want to achieve that I always show the "sub"-total like this:

DATE       NAME       PRICE      SUBTOTAL
2014-05    T-Shirt    20.00      20.00
2014-05    Jeans      45.00      65.00
2014-05    Cap        15.00      80.00

These Items are sorted by date but might have a different ID (ids dont match the index!).

I am really not able to find out how I could always calculate the subtotal (the table can be filtered by date ranges, means I could also include the items from 2014-04 and it should recalculate dynamically.

I tried it with a function like this in the controller:

var curBalanceCounter2 = 0;

$scope.currentBalanceCalc = function(finance) {
  curBalanceCounter2 = curBalanceCounter2 + finance.amount;
  return curBalanceCounter2;
}

But this i being executed 10 times so I get wrong numbers. Any better solution?

Thank you.

Jay Claiton
  • 437
  • 5
  • 17

4 Answers4

4

Create a custom filter

myApp.filter('subtotal', function(){
  return function(items, index){
    var subtotal = 0;
    for (var i = 0; i <= index; i++) {
      subtotal += items[i].price
    } 
    return subtotal || items[index].price;
  }
});

and call it like so

<li ng-repeat="item in items">{{item.name}} - {{item.price}} -
{{ items | subtotal : $index}}</li>

Demo

Since you have access to the original list (e.g. items in the code above) inside of an ng-repeat, you can pass it, along with the index of the current item, into a custom filter. This filter can then loop through each item up to and including the index passed in, and then return a summed subtotal. If the subtotal is 0 (as it would be for a first item), instead return the price of that item.

Docs: Custom filters in Angular

Marc Kline
  • 9,399
  • 1
  • 33
  • 36
  • This would work if it was ordered by ID, but it is always ordered by date. So this is getting the item by their id `items[i]` and not their date ... unfortunately, but this seems pretty close. – Jay Claiton May 22 '14 at 21:01
  • Maybe the time required isn't much, but it hurts my programmer brain to have to total the numbers so many times... – Jason Goemaat May 22 '14 at 21:19
  • 1
    What am I not understanding? If the data is already sorted by date, then the item index is also sorted according to date, right? – Jerrad May 22 '14 at 22:23
  • That's a good question. I asked something similar but deleted it because I figured they were using an `orderBy` filter on ng-repeat, which will mess up the $index ordering (and hence the above filter will not work). But as I read @JayClaiton's question again, it sounds like the array is already ordered by date. So I'm not seeing problem either. Jay, please update your question with more info on what you mean. – Marc Kline May 22 '14 at 22:29
  • 1
    I wondered if it was an orderBy issue. I've updated my answer to address that. If the data is always sorted by date though, may as well send it down from the server sorted that way. – Jerrad May 22 '14 at 22:54
  • Yeah, the indexes have an issue with the order by: http://stackoverflow.com/questions/16118762/angularjs-wrong-index-after-orderby - I will for now use this method and will try to get the data from the server already sorted this way. Even though this doesn't completely answer my question the way I expected it I will still mark it as the answer. Thank you. – Jay Claiton May 23 '14 at 13:41
2

This is similar to Marc's answer. Define a subtotal function in the controller:

$scope.subtotal = function(index){         
     var total = 0;
     angular.forEach($scope.data, function(value, key){
       if(key <= index)
         total += value.Price;           
     });
     return total;
   }

Then use it like this in the view:

<tr ng-repeat="d in data">
      <td>{{d.Date}}</td>
      <td>{{d.Name}}</td>
      <td>{{d.Price}}</td>
      <td>{{subtotal($index)}}</td>
</tr>

Demo

Update

If the issue is that the data isn't already sorted on the client, but is being sorted by a filter on the ng-repeat, then here's the fix: Pass in the orderBy parameter to the subtotal function, and execute the filter on the data before computing the subtotals:

$scope.orderBy = 'Date';
$scope.subtotal = function(index, orderBy){         
     var total = 0;
     angular.forEach($filter('orderBy')($scope.data,orderBy), function(value, key){
       if(key <= index)
         total += value.Price;           
     });
     return total;
   }

I've updated my demo with this code. You can change the sort order by changing 'Date' to 'Name' or 'Price' on this line

$scope.orderBy = 'Date';

and see that the subtotals automatically recalculate.

Community
  • 1
  • 1
Jerrad
  • 5,240
  • 1
  • 18
  • 23
1

I don't know of a way to do this in pure angular, perhaps someone will chime in.

What you need looks like a cumulative sum:

function cSum(arr) {
  var cumsum = [];

  for(var i=0;i<arr.length;i++) {
    if(i==0) cumsum[i] = arr[0];
    else cumsum[i] = cumsum[i-1] + arr[i];
  }
  return cumsum
}

Then just add that field into the array of objects that you are repeating over and you can display it in the table.

reptilicus
  • 10,290
  • 6
  • 55
  • 79
1

Not too hard to do http://jsfiddle.net/VAJ5S/3/

HTML

<div ng-app="myApp">
    <table ng-controller="myController">
        <thead>
            <tr>
                <th>DATE</th>
                <th>NAME</th>
                <th>PRICE</th>
                <th>SUBTOTAL</th>
            </tr>
        </thead>
        <tr ng-repeat="item in items">
            <td>{{item.date}}</td>
            <td>{{item.name}}</td>
            <td>{{item.price}}</td>
            <td>{{subtotal($index)}}</td>
        </tr>
    </table>
</div>

JS

var app = angular.module("myApp", []);

app.controller("myController", ["$scope", function($scope){
    $scope.items = [
        {
            date: "2014-05",
            name: "T-Shirt",
            price: 20.00
        },
        {
            date: "2014-05",
            name: "Jeans",
            price: 65.00
        },
        {
            date: "2014-05",
            name: "Cap",
            price: 80.00
        }
    ];

    $scope.subtotal = function(ind){
        var subtotal = 0;
        for (var i = 0; i<=ind; i++){
            subtotal += $scope.items[i].price;
        }
        return subtotal;
    };
}]);
SteamDev
  • 4,294
  • 5
  • 20
  • 29