Calcular la suma de elementos repetidos en AngularJS ng-repeat

107

El siguiente script muestra un carrito de la compra usando ng-repeat. Para cada elemento de la matriz, muestra el nombre del artículo, su monto y el subtotal (product.price * product.quantity ).

¿Cuál es la forma más sencilla de calcular el precio total de elementos repetidos?

<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>
mantener el melocotón
fuente
1
angular.forEach ($ scope.cart.products, function (filterObj, filterKey) {$ scope.total + = filterObj.product.price * filterObj.product.quantity;});
Gery
Ver también: stackoverflow.com/a/25667437/59087
Dave Jarvis
¿Por qué no usas tfoot-tag?
Pascal

Respuestas:

147

En plantilla

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

En controlador

$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;
}
Vamsi
fuente
24
una desventaja de esto es que itera sobre la colección dos veces. esto está bien para colecciones pequeñas, pero ¿y si la colección es bastante grande? parece que en ng-repeat debería haber una forma de tener una suma acumulada en un campo de objeto dado.
icfantv
2
@Pascamel Verifique mi respuesta ( stackoverflow.com/questions/22731145/… ) Creo que ese está trabajando para lo que preguntas con el filtro
Rajamohan Anguchamy
exactamente lo que estaba buscando cuando llegué a esa pregunta, ¡gracias por el aviso @RajaShilpa!
Pascamel
2
El problema principal con esta solución es que el total se volverá a calcular con cada resumen, porque es una llamada a función.
Marc Durdin
@icfantv, ¿cómo está iterando sobre la colección dos veces?
Crhistian Ramirez
58

Esto también funciona tanto con el filtro como con la lista normal. Lo primero es crear un nuevo filtro para la suma de todos los valores de la lista, y también dar solución para una suma de la cantidad total. En el código de detalles, verifique el enlace de violinista .

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>

mira este enlace de violín

Rajamohan Anguchamy
fuente
Oye, me estoy poniendo 'undefined'al usar el resultValue, pero si lo uso itemsfunciona bien, ¿alguna idea .. ??
Salal Aslam
Donde primero verifica el siguiente código "resultValue = (items | filter: {'details': searchFilter})", porque todos los valores de filtro se almacenan en esa variable "resultValue". Creo que te confundiste con eso {} o () esto, verifica una vez más.
Rajamohan Anguchamy
Si lo uso itemsno funcionará con filtros, ¡ayuda!
Salal Aslam
mi código es así ng-repeat="campaign in filteredCampaigns=(campaigns | filter:{'name':q})"y{{ filteredCampaigns | campaignTotal: 'totalCommission' | number: 2 }}
Salal Aslam
sí, debido a que los elementos aún no se han filtrado, después de que se haya realizado el filtro, el resultado se debe almacenar en cualquier otro modelo y solo debe usarse ese modelo. En mi muestra utilicé el modelo "resultValue".
Rajamohan Anguchamy
41

Al darme cuenta de esto respondió hace mucho tiempo, pero quería publicar un enfoque diferente no presentado ...

Use ng-initpara contar su total. De esta manera, no tiene que iterar en el HTML e iterar en el controlador. En este escenario, creo que esta es una solución más limpia / simple. (Si la lógica de conteo fuera más compleja, definitivamente recomendaría mover la lógica al controlador o al servicio según corresponda).

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

Por supuesto, en su controlador, simplemente defina / inicialice su Totalcampo:

// random controller snippet
function yourController($scope..., blah) {
    var vm = this;
    vm.Total = 0;
}
tsiorn
fuente
4
Esta es definitivamente la forma más angular. Simple, legible y declarativo. Entonces, la lógica que representa permanece donde pertenece.
Daniel Leiszen
Este método oculta el cálculo en la presentación de la celda, que es fácil de entender aquí, pero se vuelve bastante complicado con tablas complejas.
Marc Durdin
1
El otro problema con esto es que tampoco tiene enlace bidireccional.
Paul Carlton
17

Puede calcular el total interior ng-repeatsiguiente:

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

Compruebe el resultado aquí: http://plnkr.co/edit/Gb8XiCf2RWiozFI3xWzp?p=preview

En caso de resultado de actualización automática: http://plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview (Gracias - VicJordan)

Huy Nguyen
fuente
esto no funcionará cuando se filtra la lista; tbodyse inicializa solo una vez, pero trcada vez que se filtra la lista, lo que resulta en una suma incorrecta
Zbynek
¿Puedes hacer un ejemplo en plnkr o jsfiddle?
Huy Nguyen
Hmm, sí, no funciona en el filtro porque el filtro aquí solo muestra / oculta en la vista, no actualiza$scope
Huy Nguyen
@HuyNguyen, he editado tu código anterior. Consulte aquí: plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview . Aquí lo que quiero es que si el usuario cambia la cantidad, la cuarta columna (precio * cantidad) debería actualizarse automáticamente. ¿Podrías echar un vistazo a esto? Gracias
Vikasdeep Singh
9

Esta es mi solucion

filtro personalizado dulce y simple:

(pero solo relacionado con la suma simple de valores, no el producto de la suma, he creado un sumProductfiltro y lo he agregado como edición a esta publicación).

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 violín

EDITAR: sumProduct

Este es un sumProductfiltro, acepta cualquier número de argumentos. Como argumento, acepta el nombre de la propiedad de los datos de entrada y puede manejar la propiedad anidada (anidamiento marcado con un punto :) property.nested;

  • Pasar un argumento cero devuelve la longitud de los datos de entrada.
  • Pasar un solo argumento devuelve la suma simple de valores de esas propiedades.
  • Pasar más argumentos devuelve la suma de productos de valores de propiedades pasadas (suma escalar de propiedades).

aquí está JS Fiddle y el código

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 violín

Vaclav Novotny
fuente
4

Solución simple

He aquí una solución sencilla. No se requiere bucle for adicional.

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

Parte del guion

 $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;
 }
rgb
fuente
3

Otra forma de resolver esto, que se extiende desde la respuesta de Vaclav para resolver este cálculo en particular, es decir, un cálculo en cada fila.

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

Para hacer esto con un cálculo, simplemente agregue una función de cálculo a su alcance, por ejemplo

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

Usarías {{ datas|total:calcItemTotal|currency }} en tu código HTML. Esto tiene la ventaja de que no se llama para todos los resúmenes, porque utiliza filtros y se puede utilizar para totales simples o complejos.

JSFiddle

Marc Durdin
fuente
3

Esta es una forma sencilla de hacer esto con ng-repeat y ng-init para agregar todos los valores y extender el modelo con una propiedad 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>

La directiva ngInit llama a la función set total para cada elemento. La función setTotals del controlador calcula el total de cada artículo. También utiliza las variables de alcance invoiceCount y invoiceTotal para agregar (sumar) la cantidad y el total de todos los artículos.

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

para obtener más información y una demostración, consulte este enlace:

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

ozkary
fuente
1
En StackOverlow no se recomiendan los enlaces a la publicación de su blog que podrían dejar de estar vinculados. Además, cuando miro la página, aparece un error 502 Bad Gateway en el medio de su página. Responda la pregunta aquí mismo, no un enlace a otro lugar.
Rick Glos
3

Prefiero soluciones elegantes

En plantilla

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

En controlador

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

Si está utilizando ES2015 (también conocido como ES6)

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

Puede usar un filtro angular personalizado que toma la matriz de objetos del conjunto de datos y la clave en cada objeto para sumar. El filtro puede devolver la suma:

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

Luego, en su tabla para sumar una columna, puede usar:

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

Puede intentar usar los servicios de angular js, me ha funcionado ... dando los siguientes fragmentos de código

Código del controlador:

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

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

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

Código de servicio:

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;
        }
    };
});
arupjbasu
fuente
1

aquí está mi solución a este problema:

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

guión

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

reducir se ejecutará para cada producto en la gama de productos. Acumulador es la cantidad total acumulada, currentValue es el elemento actual de la matriz y el 0 en el último es el valor inicial

Fakhar Ahmad Rasul
fuente
0

Amplié un poco la respuesta de RajaShilpa. Puede utilizar una sintaxis como:

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

para que pueda acceder al objeto secundario de un objeto. Aquí está el código del filtro:

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

Tomando la respuesta de Vaclav y haciéndola más parecida a 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;
        }
    };
}]);

Esto le brinda la ventaja de acceder incluso a datos anidados y de matriz:

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

En html

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

en 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);
            };
        });
Shaik Matheen
fuente
0

La respuesta de Huy Nguyen casi está ahí. Para que funcione, agregue:

ng-repeat="_ in [ products ]"

... a la línea con ng-init. La lista siempre tiene un solo elemento, por lo que Angular repetirá el bloque exactamente una vez.

Se puede hacer que la demostración de Zybnek usando filtrado funcione agregando:

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

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

Aaron Queenan
fuente
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;
        }
venkatesh akkisetty
fuente
De la crítica: Welcome to Stack Overflow! No responda solo con el código fuente. Intente proporcionar una descripción agradable sobre cómo funciona su solución. Ver: ¿Cómo escribo una buena respuesta? . Gracias
sɐunıɔ ןɐ qɐp
0

Esta es mi solucion

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

Requiere que agregue un nombre al controlador como Controller as SomeName para que podamos almacenar en caché la variable allí (¿realmente es necesario? No estoy familiarizado con el uso de $ parent, así que no lo sé)

Luego, para cada repetición, agregue ng-init"SomeName.SumVariable = ($first ? 0 : SomeName.SumVariable) + repeatValue"

$first para verificarlo, primero, luego se restablece a cero, de lo contrario, continuaría el valor agregado

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

Thaina
fuente
-2

Después de leer todas las respuestas aquí: cómo resumir la información agrupada, decidí omitirlo todo y simplemente cargué una de las bibliotecas SQL de JavaScript. Estoy usando alasql, sí, tarda unos segundos más en el tiempo de carga, pero ahorra un tiempo incontable en la codificación y la depuración, ahora para agrupar y sumar () solo uso,

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

Sé que esto suena como una perorata sobre angular / js, pero realmente SQL resolvió esto hace más de 30 años y no deberíamos tener que reinventarlo dentro de un navegador.

Craig Debbo
fuente
1
Esto es bastante horrible. ¡Guau, SMH! Dejaré que otras personas voten en contra. Mi boca está muy abierta con esta respuesta .....
Tom Stickel