AngularJSng-repeat 中重复元素之和的计算

下面的脚本使用 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>
257427 次浏览

In Template

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

In Controller

$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;
}

This is also working both the filter and normal list. The first thing to create a new filter for the sum of all values from the list, and also given solution for a sum of the total quantity. In details code check it fiddler link.

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>

check this Fiddle Link

I expanded a bit on RajaShilpa's answer. You can use syntax like:

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

so that you can access an object's child object. Here is the code for the filter:

.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;
}
});

This is my solution

sweet and simple custom filter:

(but only related to simple sum of values, not sum product, I've made up sumProduct filter and appended it as edit to this post).

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 Fiddle

EDIT: sumProduct

This is sumProduct filter, it accepts any number of arguments. As a argument it accepts name of the property from input data, and it can handle nested property (nesting marked by dot: property.nested);

  • Passing zero argument returns length of input data.
  • Passing just one argument returns simple sum of values of that properties.
  • Passing more arguments returns sum of products of values of passed properties (scalar sum of properties).

here's JS Fiddle and the code

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 Fiddle

Realizing this answered long ago, but wanted to post different approach not presented...

Use ng-init to tally your total. This way, you do not have to iterate in the HTML and iterate in the controller. In this scenario, I think this is a cleaner/simpler solution. (If the tallying logic was more complex, I definitely would recommend moving the logic to the controller or service as appropriate.)

    <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>

Of course, in your controller, simply define/initialize your Total field:

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

Taking Vaclav's answer and making it more Angular-like:

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;
}
};
}]);

This gives you the benefit of even accessing nested and array data:

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

This is a simple way to do this with ng-repeat and ng-init to aggregate all the values and extend the model with a item.total property.

<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>

The ngInit directive calls the set total function for each item. The setTotals function in the controller calculates each item total. It also uses the invoiceCount and invoiceTotal scope variables to aggregate (sum) the quantity and total for all items.

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

for more information and demo look at this link:

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

Another way of solving this, extending from Vaclav's answer to solve this particular calculation — i.e. a calculation on each row.

    .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;
}
};
})

To do this with a calculation, just add a calculation function to your scope, e.g.

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

You would use \{\{ datas|total:calcItemTotal|currency }} in your HTML code. This has the advantage of not being called for every digest, because it uses filters, and can be used for simple or complex totals.

JSFiddle

I prefer elegant solutions

In Template

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

In Controller

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

If you're using ES2015 (aka ES6)

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

You can calculate total inside ng-repeat follow:

<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>

Check result here: http://plnkr.co/edit/Gb8XiCf2RWiozFI3xWzp?p=preview

In case automatic update result: http://plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview (Thanks – VicJordan)

After reading all the answers here - how to summarize grouped information, i decided to skip it all and just loaded one of the SQL javascript libraries. I'm using alasql, yeah it takes a few secs longer on load time but saves countless time in coding and debugging, Now to group and sum() I just use,

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

I know this sounds like a bit of a rant on angular/js but really SQL solved this 30+ years ago and we shouldn't have to re-invent it within a browser.

In html

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

in 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);
};
});

Simple Solution

Here is a simple solution. No additional for loop required.

HTML part

         <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>

Script Part

 $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;
}

Huy Nguyen's answer is almost there. To make it work, add:

ng-repeat="_ in [ products ]"

...to the line with ng-init. The list always has a single item, so Angular will repeat the block exactly once.

Zybnek's demo using filtering can be made to work by adding:

ng-repeat="_ in [ [ products, search ] ]"

See http://plnkr.co/edit/dLSntiy8EyahZ0upDpgy?p=preview.

here is my solution to this problem:

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

script

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

reduce will execute for each product in products array. Accumulator is the total accumulated amount, currentValue is the current element of the array and the 0 in the last is the initial value

You may try using services of angular js, it has worked for me..giving the code snippets below

Controller code:

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


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


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

Service Code:

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;
}
};
});

You can use a custom Angular filter that takes the dataset object array and the key in each object to sum. The filter can then return the sum:

.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;
};
})

Then in your table to sum a column you can use:

<th>\{\{ dataSet | sumColumn: 'keyInObjectToSum' }}</th>
**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;
}

This is my solution

<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>

It require you to add name to controller as Controller as SomeName so we can cache variable in there (is it really require? I don't familiar with using $parent so I don't know)

Then for each repeat, add ng-init"SomeName.SumVariable = ($first ? 0 : SomeName.SumVariable) + repeatValue"

$first for checking it is first then it reset to zero, else it would continue aggregate value

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

I usually use this simple code that below. Make sure vm.myArray list not be 'null' in calculating.

vm.totalQuantity = 0;
$.each(vm.myArray, function (i, v) {
vm.totalQuantity += v.Quantity;
});