Calcul de la somme des éléments répétés dans AngularJS ng-repeat

107

Le script ci-dessous affiche un panier à l'aide de ng-repeat. Pour chaque élément du tableau, il affiche le nom de l'élément, son montant et le sous-total ( product.price * product.quantity).

Quelle est la manière la plus simple de calculer le prix total des éléments répétés?

<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>
garder la pêche
la source
1
angular.forEach ($ scope.cart.products, function (filterObj, filterKey) {$ scope.total + = filterObj.product.price * filterObj.product.quantity;});
Gery
Voir aussi: stackoverflow.com/a/25667437/59087
Dave Jarvis
Pourquoi n'utilisez-vous pas tfoot-tag?
Pascal

Réponses:

147

Dans le modèle

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

Dans le contrôleur

$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
la source
24
un inconvénient est qu'il se répète deux fois sur la collection. c'est bien pour les petites collections, mais que faire si la collection est plutôt grande? il semble que dans ng-repeat, il devrait y avoir un moyen d'avoir une somme en cours sur un champ objet donné.
icfantv
2
@Pascamel Vérifiez ma réponse ( stackoverflow.com/questions/22731145/… ) je pense que celui-là fonctionne pour ce que vous demandez avec filtre
Rajamohan Anguchamy
exactement ce que je cherchais quand j'ai atterri sur cette question, merci pour le heads up @RajaShilpa!
Pascamel
2
Le principal problème avec cette solution est que le total sera recalculé à chaque résumé, car il s'agit d'un appel de fonction.
Marc Durdin
@icfantv comment itère-t-il deux fois sur la collection?
Crhistian Ramirez
58

Cela fonctionne également à la fois le filtre et la liste normale. La première chose à faire pour créer un nouveau filtre pour la somme de toutes les valeurs de la liste, ainsi qu'une solution donnée pour une somme de la quantité totale. Dans les détails, vérifiez le lien du violoniste .

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>

vérifier ce lien Fiddle

Rajamohan Anguchamy
la source
Hé, je reçois 'undefined'en utilisant le resultValue, mais si je l'utilise, itemscela fonctionne bien, des idées .. ??
Salal Aslam
Où vous vérifiez d'abord le code suivant "resultValue = (items | filter: {'details': searchFilter})", car toutes les valeurs de filtre sont stockées dans cette variable "resultValue". Je pense que vous vous êtes trompé {} ou () ceci, vérifiez encore une fois.
Rajamohan Anguchamy
Si je l'utilise, itemscela ne fonctionnera pas avec les filtres, aidez-moi!
Salal Aslam
mon code est comme ça ng-repeat="campaign in filteredCampaigns=(campaigns | filter:{'name':q})"et{{ filteredCampaigns | campaignTotal: 'totalCommission' | number: 2 }}
Salal Aslam
oui, parce que les éléments ne sont pas encore filtrés, une fois le filtre effectué, ce résultat doit être stocké dans n'importe quel autre modèle et ne doit utiliser que ce modèle. Dans mon exemple, j'ai utilisé le modèle "resultValue".
Rajamohan Anguchamy
41

Réaliser cela a répondu il y a longtemps, mais je voulais publier une approche différente non présentée ...

Utilisez ng-initpour calculer votre total. De cette façon, vous n'avez pas besoin d'itérer dans le HTML et d'itérer dans le contrôleur. Dans ce scénario, je pense que c'est une solution plus propre / plus simple. (Si la logique de comptage était plus complexe, je recommanderais certainement de déplacer la logique vers le contrôleur ou le service, le cas échéant.)

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

Bien entendu, dans votre contrôleur, définissez / initialisez simplement votre Totalchamp:

// random controller snippet
function yourController($scope..., blah) {
    var vm = this;
    vm.Total = 0;
}
Tsiorn
la source
4
C'est certainement la manière la plus anguleuse. Simple, lisible et déclaratif. Ainsi, la logique qu'elle représente reste à sa place.
Daniel Leiszen du
Cette méthode masque le calcul dans la présentation de la cellule, ce qui est facile à comprendre ici, mais devient assez compliqué avec des tableaux complexes.
Marc Durdin
1
L'autre problème avec ceci est qu'il n'a pas non plus de liaison bidirectionnelle.
Paul Carlton
17

Vous pouvez calculer le total à l'intérieur de la ng-repeatsuite:

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

Vérifiez le résultat ici: http://plnkr.co/edit/Gb8XiCf2RWiozFI3xWzp?p=preview

En cas de résultat de la mise à jour automatique: http://plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview (Merci - VicJordan)

Huy Nguyen
la source
cela ne fonctionnera pas lorsque la liste est filtrée - tbodyest initialisé une seule fois, mais trchaque fois que la liste est filtrée, ce qui entraîne une somme incorrecte
Zbynek
Pouvez-vous faire un exemple sur plnkr ou jsfiddle?
Huy Nguyen
Hmm, oui, cela ne fonctionne pas dans le filtre parce que le filtre ici affiche / masque simplement la vue, pas la mise à jour$scope
Huy Nguyen
@HuyNguyen, j'ai édité votre code ci-dessus. Veuillez vérifier ici: plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview . Ici, ce que je veux, c'est que si l'utilisateur change la quantité, la 4ème colonne (prix * quantité) doit être mise à jour automatiquement. Pourriez-vous s'il vous plaît jeter un oeil à cela. Merci
Vikasdeep Singh
9

C'est ma solution

filtre personnalisé doux et simple:

(mais uniquement lié à une simple somme de valeurs, pas à un produit de somme, j'ai créé un sumProductfiltre et l' ai ajouté en tant que modification à cet article).

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

C'est un sumProductfiltre, il accepte n'importe quel nombre d'arguments. En tant qu'argument, il accepte le nom de la propriété à partir des données d'entrée, et il peut gérer la propriété imbriquée (imbrication marquée par un point:) property.nested;

  • Passer l'argument zéro renvoie la longueur des données d'entrée.
  • Passer un seul argument renvoie une simple somme des valeurs de ces propriétés.
  • Passer plus d'arguments renvoie la somme des produits des valeurs des propriétés passées (somme scalaire des propriétés).

voici JS Fiddle et le 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

Vaclav Novotny
la source
4

Solution simple

Voici une solution simple. Aucune boucle for supplémentaire requise.

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

Partie de script

 $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;
 }
RVB
la source
3

Une autre façon de résoudre ce problème, s'étendant de la réponse de Vaclav pour résoudre ce calcul particulier - c'est-à-dire un calcul sur chaque ligne.

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

Pour ce faire avec un calcul, ajoutez simplement une fonction de calcul à votre périmètre, par exemple

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

Vous utiliseriez {{ datas|total:calcItemTotal|currency }} dans votre code HTML. Cela présente l'avantage de ne pas être appelé pour chaque résumé, car il utilise des filtres et peut être utilisé pour des totaux simples ou complexes.

JSFiddle

Marc Durdin
la source
3

C'est un moyen simple de le faire avec ng-repeat et ng-init pour agréger toutes les valeurs et étendre le modèle avec une propriété 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 directive ngInit appelle la fonction set total pour chaque élément. La fonction setTotals du contrôleur calcule le total de chaque élément. Il utilise également les variables de portée factureCount et factureTotal pour agréger (additionner) la quantité et le total de tous les articles.

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

pour plus d'informations et une démonstration, consultez ce lien:

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

Ozkary
la source
1
Les liens vers votre article de blog qui pourraient devenir un lien mort sont déconseillés sur StackOverlow. De plus, lorsque je regarde la page, j'obtiens une erreur 502 Bad Gateway au milieu de votre page. Répondez à la question ici pas un lien vers ailleurs.
Rick Glos
3

Je préfère les solutions élégantes

Dans le modèle

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

Dans le contrôleur

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

Si vous utilisez ES2015 (alias ES6)

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

Vous pouvez utiliser un filtre angulaire personnalisé qui prend en compte le tableau d'objets de l'ensemble de données et la clé de chaque objet. Le filtre peut alors renvoyer la somme:

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

Ensuite, dans votre tableau pour additionner une colonne, vous pouvez utiliser:

<th>{{ dataSet | sumColumn: 'keyInObjectToSum' }}</th>
Jakecyr
la source
1

Vous pouvez essayer d'utiliser les services de angular js, cela a fonctionné pour moi ... en donnant les extraits de code ci-dessous

Code contrôleur:

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

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

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

Code de service:

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
la source
1

voici ma solution à ce problème:

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

scénario

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

réduire s'exécutera pour chaque produit de la gamme de produits. Accumulator est le montant total accumulé, currentValue est l'élément actuel du tableau et le 0 dans le dernier est la valeur initiale

Fakhar Ahmad Rasul
la source
0

J'ai développé un peu la réponse de RajaShilpa. Vous pouvez utiliser une syntaxe comme:

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

afin que vous puissiez accéder à l'objet enfant d'un objet. Voici le code du filtre:

.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
la source
0

Prendre la réponse de Vaclav et la rendre plus angulaire:

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

Cela vous donne même l'avantage d'accéder aux données imbriquées et de tableau:

{{data | total:'values[0].value'}}
Sonate
la source
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
la source
0

La réponse de Huy Nguyen est presque là. Pour le faire fonctionner, ajoutez:

ng-repeat="_ in [ products ]"

... à la ligne avec ng-init. La liste a toujours un seul élément, donc Angular répétera le bloc exactement une fois.

La démo de Zybnek utilisant le filtrage peut être mise en œuvre en ajoutant:

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

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

Aaron Queenan
la source
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
la source
De l'avis: Bienvenue à Stack Overflow! Veuillez ne pas répondre uniquement avec le code source. Essayez de fournir une belle description du fonctionnement de votre solution. Voir: Comment rédiger une bonne réponse? . Merci
sɐunıɔ ןɐ qɐp
0

C'est ma 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>

Cela nécessite que vous ajoutiez un nom au contrôleur Controller as SomeNameafin que nous puissions mettre en cache la variable (est-ce vraiment nécessaire? Je ne suis pas familier avec l'utilisation de $ parent donc je ne sais pas)

Puis pour chaque répétition, ajoutez ng-init"SomeName.SumVariable = ($first ? 0 : SomeName.SumVariable) + repeatValue"

$first pour le vérifier, il est d'abord remis à zéro, sinon il continuerait la valeur agrégée

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

Thaina
la source
-2

Après avoir lu toutes les réponses ici - comment résumer les informations groupées, j'ai décidé de tout ignorer et je viens de charger l'une des bibliothèques javascript SQL. J'utilise alasql, ouais cela prend quelques secondes de plus sur le temps de chargement mais économise un temps infini dans le codage et le débogage, maintenant pour grouper et sum () je viens d'utiliser,

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

Je sais que cela ressemble un peu à une diatribe sur angular / js, mais SQL a vraiment résolu cela il y a plus de 30 ans et nous ne devrions pas avoir à le réinventer dans un navigateur.

Craig Debbo
la source
1
C'est juste assez horrible. Juste wow SMH - Je vais laisser les autres voter. Ma bouche est grande ouverte avec cette réponse .....
Tom Stickel