计算AngularJS ng-repeat中重复元素的总和


107

以下脚本使用来显示购物车ng-repeat。对于数组中的每个元素,它都会显示项目名称,其数量和小计(product.price * product.quantity)。

计算重复元素总价的最简单方法是什么?

<table>

    <tr>
        <th>Product</th>
        <th>Quantity</th>
        <th>Price</th>
    </tr>

    <tr ng-repeat="product in cart.products">
        <td>{{product.name}}</td>
        <td>{{product.quantity}}</td>
        <td>{{product.price * product.quantity}} €</td>
    </tr>

    <tr>
        <td></td>
        <td>Total :</td>
        <td></td> <!-- Here is the total value of my cart -->
    </tr>

</table>

1
angular.forEach($ scope.cart.products,function(filterObj,filterKey){$ scope.total + = filterObj.product.price * filterObj.product.quantity;});
杰里2015年



你为什么不使用tfoot-tag?
Pascal

Answers:


147

在模板中

<td>Total: {{ getTotal() }}</td>

在控制器中

$scope.getTotal = function(){
    var total = 0;
    for(var i = 0; i < $scope.cart.products.length; i++){
        var product = $scope.cart.products[i];
        total += (product.price * product.quantity);
    }
    return total;
}

24
这样做的一个缺点是对集合进行两次迭代。这对于小型馆藏来说很好,但是如果馆藏相当大怎么办?似乎在ng-repeat中应该有一种方法可以在给定的对象字段上获得运行总和。
icfantv 2014年

2
@Pascamel检查我的答案(stackoverflow.com/questions/22731145/…)我认为那个
可以满足

当我遇到这个问题时,正是我想要的东西,谢谢@RajaShilpa的注意!
Pascamel

2
该解决方案的主要问题在于,每个摘要都将重新计算总数,因为这是一个函数调用。
Marc Durdin

@icfantv如何遍历集合两次?
克里斯蒂安·拉米雷斯

58

这也适用于过滤器列表和常规列表。首先,为列表中所有值的总和创建一个新的过滤器,并给出总和的解决方案。在详细代码中检查它的提琴手链接

angular.module("sampleApp", [])
        .filter('sumOfValue', function () {
        return function (data, key) {        
            if (angular.isUndefined(data) || angular.isUndefined(key))
                return 0;        
            var sum = 0;        
            angular.forEach(data,function(value){
                sum = sum + parseInt(value[key], 10);
            });        
            return sum;
        }
    }).filter('totalSumPriceQty', function () {
        return function (data, key1, key2) {        
            if (angular.isUndefined(data) || angular.isUndefined(key1)  || angular.isUndefined(key2)) 
                return 0;        
            var sum = 0;
            angular.forEach(data,function(value){
                sum = sum + (parseInt(value[key1], 10) * parseInt(value[key2], 10));
            });
            return sum;
        }
    }).controller("sampleController", function ($scope) {
        $scope.items = [
          {"id": 1,"details": "test11","quantity": 2,"price": 100}, 
          {"id": 2,"details": "test12","quantity": 5,"price": 120}, 
          {"id": 3,"details": "test3","quantity": 6,"price": 170}, 
          {"id": 4,"details": "test4","quantity": 8,"price": 70}
        ];
    });


<div ng-app="sampleApp">
  <div ng-controller="sampleController">
    <div class="col-md-12 col-lg-12 col-sm-12 col-xsml-12">
      <label>Search</label>
      <input type="text" class="form-control" ng-model="searchFilter" />
    </div>
    <div class="col-md-12 col-lg-12 col-sm-12 col-xsml-12">
      <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2">
        <h4>Id</h4>

      </div>
      <div class="col-md-4 col-lg-4 col-sm-4 col-xsml-4">
        <h4>Details</h4>

      </div>
      <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2 text-right">
        <h4>Quantity</h4>

      </div>
      <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2 text-right">
        <h4>Price</h4>

      </div>
      <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2 text-right">
        <h4>Total</h4>

      </div>
      <div ng-repeat="item in resultValue=(items | filter:{'details':searchFilter})">
        <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2">{{item.id}}</div>
        <div class="col-md-4 col-lg-4 col-sm-4 col-xsml-4">{{item.details}}</div>
        <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2 text-right">{{item.quantity}}</div>
        <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2 text-right">{{item.price}}</div>
        <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2 text-right">{{item.quantity * item.price}}</div>
      </div>
      <div colspan='3' class="col-md-8 col-lg-8 col-sm-8 col-xsml-8 text-right">
        <h4>{{resultValue | sumOfValue:'quantity'}}</h4>

      </div>
      <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2 text-right">
        <h4>{{resultValue | sumOfValue:'price'}}</h4>

      </div>
      <div class="col-md-2 col-lg-2 col-sm-2 col-xsml-2 text-right">
        <h4>{{resultValue | totalSumPriceQty:'quantity':'price'}}</h4>

      </div>
    </div>
  </div>
</div>

检查此小提琴链接


嘿,我正在'undefined'使用的时候resultValue,但如果我用items它工作正常,任何想法..?
Salal Aslam

首先检查以下代码“ resultValue =(items | filter:{'details':searchFilter})”,因为所有过滤器值都存储在该变量“ resultValue”中。我认为您误认为是{}或(),请再次验证。
Rajamohan Anguchamy 2015年

如果我使用items它不能与过滤器一起使用,请帮助!
2015年

我的代码是这样 ng-repeat="campaign in filteredCampaigns=(campaigns | filter:{'name':q})"{{ filteredCampaigns | campaignTotal: 'totalCommission' | number: 2 }}
沙巴阿斯拉姆

是的,因为尚未过滤项目,所以发生过滤后,结果必须存储到任何其他模型中,并且只能使用该模型。在我的示例中,我使用了“ resultValue”模型。
Rajamohan Anguchamy 2015年

41

早就意识到了这一点,但想发布一种未介绍的其他方法...

使用ng-init相符您的总。这样,您不必在HTML中进行迭代,也不必在控制器中进行迭代。在这种情况下,我认为这是一种更简洁的解决方案。(如果统计逻辑更加复杂,我绝对会建议将逻辑适当地移至控制器或服务。)

    <tr>
        <th>Product</th>
        <th>Quantity</th>
        <th>Price</th>
    </tr>

    <tr ng-repeat="product in cart.products">
        <td>{{product.name}}</td>
        <td>{{product.quantity}}</td>
        <td ng-init="itemTotal = product.price * product.quantity; controller.Total = controller.Total + itemTotal">{{itemTotal}} €</td>
    </tr>

    <tr>
        <td></td>
        <td>Total :</td>
        <td>{{ controller.Total }}</td> // Here is the total value of my cart
    </tr>

当然,在您的控制器中,只需定义/初始化您的Total字段即可:

// random controller snippet
function yourController($scope..., blah) {
    var vm = this;
    vm.Total = 0;
}

4
这绝对是最有角度的方式。简单,可读和声明。因此,它所代表的逻辑仍然存在。
丹尼尔·莱森

该方法将计算隐藏在单元格表示中,这在这里很容易理解,但是对于复杂的表却非常混乱。
Marc Durdin

1
与此相关的另一个问题是它也没有双向绑定。
保罗·卡尔顿

17

您可以ng-repeat按照以下步骤计算总计:

<tbody ng-init="total = 0">
  <tr ng-repeat="product in products">
    <td>{{ product.name }}</td>
    <td>{{ product.quantity }}</td>
    <td ng-init="$parent.total = $parent.total + (product.price * product.quantity)">${{ product.price * product.quantity }}</td>
  </tr>
  <tr>
    <td>Total</td>
    <td></td>
    <td>${{ total }}</td>
  </tr>
</tbody>

在此处检查结果:http : //plnkr.co/edit/Gb8XiCf2RWiozFI3xWzp?p=preview

如果有自动更新结果:http ://plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview(感谢– VicJordan)


当列表被过滤时,这将不起作用- tbody仅初始化一次,但是tr每次列表被过滤时,导致不正确的总和
Zbynek

您可以在plnkr或jsfiddle上举个例子吗?

嗯,是的,它在过滤器中不起作用,因为此处的过滤器仅在视图中显示/隐藏,而不是更新$scope
Huy Nguyen

@HuyNguyen,我已经编辑了您上面的代码。请在此处检查:plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview 。这里我要的是如果用户更改数量,则第4列(价格*数量)应自动更新。你能看看这个吗。谢谢
Vikasdeep Singh

9

这是我的解决方案

简单易用的自定义过滤器:

(但仅与简单的值总和有关,与总和无关,我已经制作了sumProduct过滤器,并将其作为编辑内容附加到了这篇文章中)。

angular.module('myApp', [])

    .filter('total', function () {
        return function (input, property) {
            var i = input instanceof Array ? input.length : 0;
// if property is not defined, returns length of array
// if array has zero length or if it is not an array, return zero
            if (typeof property === 'undefined' || i === 0) {
                return i;
// test if property is number so it can be counted
            } else if (isNaN(input[0][property])) {
                throw 'filter total can count only numeric values';
// finaly, do the counting and return total
            } else {
                var total = 0;
                while (i--)
                    total += input[i][property];
                return total;
            }
        };
    })

JS小提琴

编辑:sumProduct

这是sumProduct过滤器,它接受任意数量的参数。作为参数,它接受输入数据中的属性名称,并且可以处理嵌套的属性(由dot:标记的嵌套property.nested);

  • 传递零参数将返回输入数据的长度。
  • 仅传递一个参数将返回该属性的简单值之和。
  • 传递更多的参数将返回传递的属性的值的乘积之和(属性的标量之和)。

这是JS Fiddle和代码

angular.module('myApp', [])
    .filter('sumProduct', function() {
        return function (input) {
            var i = input instanceof Array ? input.length : 0;
            var a = arguments.length;
            if (a === 1 || i === 0)
                return i;

            var keys = [];
            while (a-- > 1) {
                var key = arguments[a].split('.');
                var property = getNestedPropertyByKey(input[0], key);
                if (isNaN(property))
                    throw 'filter sumProduct can count only numeric values';
                keys.push(key);
            }

            var total = 0;
            while (i--) {
                var product = 1;
                for (var k = 0; k < keys.length; k++)
                    product *= getNestedPropertyByKey(input[i], keys[k]);
                total += product;
            }
            return total;

            function getNestedPropertyByKey(data, key) {
                for (var j = 0; j < key.length; j++)
                    data = data[key[j]];
                return data;
            }
        }
    })

JS小提琴


4

简单的解决方案

这是一个简单的解决方案。无需其他for循环。

HTML部分

         <table ng-init="ResetTotalAmt()">
                <tr>
                    <th>Product</th>
                    <th>Quantity</th>
                    <th>Price</th>
                </tr>

                <tr ng-repeat="product in cart.products">
                    <td ng-init="CalculateSum(product)">{{product.name}}</td>
                    <td>{{product.quantity}}</td>
                    <td>{{product.price * product.quantity}} €</td>
                </tr>

                <tr>
                    <td></td>
                    <td>Total :</td>
                    <td>{{cart.TotalAmt}}</td> // Here is the total value of my cart
                </tr>

           </table>

脚本部分

 $scope.cart.TotalAmt = 0;
 $scope.CalculateSum= function (product) {
   $scope.cart.TotalAmt += (product.price * product.quantity);
 }
//It is enough to Write code $scope.cart.TotalAmt =0; in the function where the cart.products get allocated value. 
$scope.ResetTotalAmt = function (product) {
   $scope.cart.TotalAmt =0;
 }

3

解决此问题的另一种方法是从Vaclav的答案扩展到解决此特定计算-即每行的计算。

    .filter('total', function () {
        return function (input, property) {
            var i = input instanceof Array ? input.length : 0;
            if (typeof property === 'undefined' || i === 0) {
                return i;
            } else if (typeof property === 'function') {
                var total = 0; 
                while (i--)
                    total += property(input[i]);
                return total;
            } else if (isNaN(input[0][property])) {
                throw 'filter total can count only numeric values';
            } else {
                var total = 0;
                while (i--)
                    total += input[i][property];
                return total;
            }
        };
    })

为此,只需在您的范围内添加一个计算函数即可,例如

$scope.calcItemTotal = function(v) { return v.price*v.quantity; };

您将{{ datas|total:calcItemTotal|currency }}在HTML代码中使用。这样做的优点是不必为每个摘要都调用它,因为它使用过滤器,并且可以用于简单或复杂的总计。

JSFiddle


3

这是使用ng-repeat和ng-init聚合所有值并使用item.total属性扩展模型的简单方法。

<table>
<tr ng-repeat="item in items" ng-init="setTotals(item)">
                    <td>{{item.name}}</td>
                    <td>{{item.quantity}}</td>
                    <td>{{item.unitCost | number:2}}</td>
                    <td>{{item.total | number:2}}</td>
</tr>
<tr class="bg-warning">
                    <td>Totals</td>
                    <td>{{invoiceCount}}</td>
                    <td></td>                    
                    <td>{{invoiceTotal | number:2}}</td>
                </tr>
</table>

ngInit指令为每个项目调用设置的total函数。控制器中的setTotals函数计算每个项目的总计。它还使用invoiceCount和invoiceTotal范围变量来汇总(总和)所有项目的数量和总计。

$scope.setTotals = function(item){
        if (item){
            item.total = item.quantity * item.unitCost;
            $scope.invoiceCount += item.quantity;
            $scope.invoiceTotal += item.total;
        }
    }

有关更多信息和演示,请查看此链接:

http://www.ozkary.com/2015/06/angularjs-calculate-totals-using.html


1
在StackOverlow上不建议链接到您的博客文章的链接可能会失效。另外,当我查看该页面时,您在页面中间收到502 Bad Gateway错误。在这里回答问题,而不是其他地方的链接。
里克·格洛斯

3

我更喜欢优雅的解决方案

在模板中

<td>Total: {{ totalSum }}</td>

在控制器中

$scope.totalSum = Object.keys(cart.products).map(function(k){
    return +cart.products[k].price;
}).reduce(function(a,b){ return a + b },0);

如果您使用的是ES2015(又名ES6)

$scope.totalSum = Object.keys(cart.products)
  .map(k => +cart.products[k].price)
  .reduce((a, b) => a + b);

2

您可以使用自定义Angular过滤器,该过滤器将数据集对象数组和每个对象中的键求和。过滤器然后可以返回总和:

.filter('sumColumn', function(){
        return function(dataSet, columnToSum){
            let sum = 0;

            for(let i = 0; i < dataSet.length; i++){
                sum += parseFloat(dataSet[i][columnToSum]) || 0;
            }

            return sum;
        };
    })

然后在表格中汇总一栏,您可以使用:

<th>{{ dataSet | sumColumn: 'keyInObjectToSum' }}</th>

1

您可以尝试使用angular js服务,它对我有用。.给出下面的代码片段

控制器代码:

$scope.total = 0;
var aCart = new CartService();

$scope.addItemToCart = function (product) {
    aCart.addCartTotal(product.Price);
};

$scope.showCart = function () {    
    $scope.total = aCart.getCartTotal();
};

服务代码:

app.service("CartService", function () {

    Total = [];
    Total.length = 0;

    return function () {

        this.addCartTotal = function (inTotal) {
            Total.push( inTotal);
        }

        this.getCartTotal = function () {
            var sum = 0;
            for (var i = 0; i < Total.length; i++) {
                sum += parseInt(Total[i], 10); 
            }
            return sum;
        }
    };
});

1

这是我对这个问题的解决方案:

<td>Total: {{ calculateTotal() }}</td>

脚本

$scope.calculateVAT = function () {
    return $scope.cart.products.reduce((accumulator, currentValue) => accumulator + (currentValue.price * currentValue.quantity), 0);
};

reduce将对products数组中的每个产品执行。累加器是总累加量,currentValue是数组的当前元素,最后一个0是初始值


0

我扩展了RajaShilpa的答案。您可以使用如下语法:

{{object | sumOfTwoValues:'quantity':'products.productWeight'}}

这样您就可以访问对象的子对象。这是过滤器的代码:

.filter('sumOfTwoValues', function () {
    return function (data, key1, key2) {
        if (typeof (data) === 'undefined' || typeof (key1) === 'undefined' || typeof (key2) === 'undefined') {
            return 0;
        }
        var keyObjects1 = key1.split('.');
        var keyObjects2 = key2.split('.');
        var sum = 0;
        for (i = 0; i < data.length; i++) {
            var value1 = data[i];
            var value2 = data[i];
            for (j = 0; j < keyObjects1.length; j++) {
                value1 = value1[keyObjects1[j]];
            }
            for (k = 0; k < keyObjects2.length; k++) {
                value2 = value2[keyObjects2[k]];
            }
            sum = sum + (value1 * value2);
        }
        return sum;
    }
});

0

接受Vaclav的回答,使其更像Angular:

angular.module('myApp').filter('total', ['$parse', function ($parse) {
    return function (input, property) {
        var i = input instanceof Array ? input.length : 0,
            p = $parse(property);

        if (typeof property === 'undefined' || i === 0) {
            return i;
        } else if (isNaN(p(input[0]))) {
            throw 'filter total can count only numeric values';
        } else {
            var total = 0;
            while (i--)
                total += p(input[i]);
            return total;
        }
    };
}]);

这使您甚至可以访问嵌套和数组数据:

{{data | total:'values[0].value'}}

0

在html中

<b class="text-primary">Total Amount: ${{ data.allTicketsTotalPrice() }}</b>

在JavaScript中

  app.controller('myController', function ($http) {
            var vm = this;          
            vm.allTicketsTotalPrice = function () {
                var totalPrice = 0;
                angular.forEach(vm.ticketTotalPrice, function (value, key) {
                    totalPrice += parseFloat(value);
                });
                return totalPrice.toFixed(2);
            };
        });


0
**Angular 6: Grand Total**       
 **<h2 align="center">Usage Details Of {{profile$.firstName}}</h2>
        <table align ="center">
          <tr>
            <th>Call Usage</th>
            <th>Data Usage</th>
            <th>SMS Usage</th>
            <th>Total Bill</th>
          </tr>
          <tr>
          <tr *ngFor="let user of bills$">
            <td>{{ user.callUsage}}</td>
            <td>{{ user.dataUsage }}</td>
            <td>{{ user.smsUsage }}</td>
       <td>{{user.callUsage *2 + user.dataUsage *1 + user.smsUsage *1}}</td>
          </tr>


          <tr>
            <th> </th>
            <th>Grand Total</th>
            <th></th>
            <td>{{total( bills$)}}</td>
          </tr>
        </table>**


    **Controller:**
        total(bills) {
            var total = 0;
            bills.forEach(element => {
total = total + (element.callUsage * 2 + element.dataUsage * 1 + element.smsUsage * 1);
            });
            return total;
        }

点评来源:欢迎来到Stack Overflow!请不要只回答源代码。尝试提供有关您的解决方案如何工作的很好的描述。请参阅:我如何写一个好的答案?。谢谢
sɐunıɔןɐqɐp

0

这是我的解决方案

<div ng-controller="MainCtrl as mc">
  <ul>
      <li ng-repeat="n in [1,2,3,4]" ng-init="mc.sum = ($first ? 0 : mc.sum) + n">{{n}}</li>
      <li>sum : {{mc.sum}}</li>
  </ul>
</div>

它需要您为控制器添加名称,Controller as SomeName以便我们可以在其中缓存变量(这真的需要吗?我不熟悉使用$ parent,所以我不知道)

然后对于每个重复,添加 ng-init"SomeName.SumVariable = ($first ? 0 : SomeName.SumVariable) + repeatValue"

$first 为了检查它是第一个然后重置为零,否则它将继续聚合值

http://jsfiddle.net/thainayu/harcv74f/


-2

在阅读完所有答案后(如何总结分组信息),我决定跳过所有信息并仅加载其中一个SQL javascript库。我使用的是alasql,是的,加载时间要花几秒钟,但可以节省无数的编码和调试时间,现在我只使用group和sum(),

$scope.bySchool = alasql('SELECT School, SUM(Cost) AS Cost from ? GROUP BY School',[restResults]);

我知道这听起来像是对angular / js的咆哮,但真正的SQL在30年前解决了这个问题,我们不必在浏览器中重新发明它。


1
这太可怕了。哇,SMH哇-我会让其他人投反对票。这个答案让我张大了嘴..
汤姆·斯蒂克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.