Calcolo della somma di elementi ripetuti in AngularJS ng-repeat


107

Lo script seguente mostra un carrello del negozio utilizzando ng-repeat. Per ogni elemento dell'array, mostra il nome dell'elemento, la sua quantità e il totale parziale ( product.price * product.quantity).

Qual è il modo più semplice per calcolare il prezzo totale di elementi ripetuti?

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



Perché non usi tfoot-tag?
Pascal

Risposte:


147

Nel modello

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

24
uno svantaggio di questo è che itera sulla raccolta due volte. questo va bene per le piccole collezioni, ma cosa succede se la collezione è piuttosto grande? sembra che in ng-repeat ci dovrebbe essere un modo per avere una somma parziale su un dato campo oggetto.
icfantv

2
@Pascamel Controlla la mia risposta ( stackoverflow.com/questions/22731145/… ) Penso che quello che lavora per quello che chiedi con il filtro
Rajamohan Anguchamy

esattamente quello che stavo cercando quando sono arrivato su questa domanda, grazie per l'avvertimento @RajaShilpa!
Pascamel

2
Il problema principale con questa soluzione è che il totale verrà ricalcolato con ogni digest, perché è una chiamata di funzione.
Marc Durdin

@icfantv come sta ripetendo due volte la raccolta?
Crhistian Ramirez

58

Funziona anche con il filtro e con l'elenco normale. La prima cosa per creare un nuovo filtro per la somma di tutti i valori dalla lista e anche data la soluzione per una somma della quantità totale. Nel codice dei dettagli controlla il link del 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>

controlla questo Fiddle Link


Ehi, sto ottenendo 'undefined'quando utilizzo il resultValue, ma se lo uso itemsfunziona bene, qualche idea .. ??
Salal Aslam

Dove per prima cosa controlli il seguente codice "resultValue = (items | filter: {'details': searchFilter})", perché tutti i valori di filtro vengono memorizzati in quella variabile "resultValue". Penso che tu abbia scambiato per questo {} o () questo, verifica ancora una volta.
Rajamohan Anguchamy

Se lo uso itemsnon funzionerà con i filtri, aiuto!
Salal Aslam

il mio codice è così ng-repeat="campaign in filteredCampaigns=(campaigns | filter:{'name':q})"e{{ filteredCampaigns | campaignTotal: 'totalCommission' | number: 2 }}
Salal Aslam

sì, poiché gli elementi non sono ancora filtrati, dopo che il filtro è stato eseguito, il risultato deve essere memorizzato su qualsiasi altro modello e deve utilizzare solo quel modello. Nel mio esempio ho usato il modello "resultValue".
Rajamohan Anguchamy

41

Realizzando questo ha risposto molto tempo fa, ma volevo pubblicare un approccio diverso non presentato ...

Utilizzare ng-initper calcolare il totale. In questo modo, non è necessario iterare nell'HTML e iterare nel controller. In questo scenario, penso che questa sia una soluzione più pulita / più semplice. (Se la logica di conteggio fosse più complessa, consiglierei sicuramente di spostare la logica nel controller o nel servizio a seconda dei casi.)

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

Ovviamente, nel tuo controller, definisci / inizializza semplicemente il tuo Totalcampo:

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

4
Questo è sicuramente il modo più angolare. Semplice, leggibile e dichiarativo. Quindi, la logica che rappresenta rimane a cui appartiene.
Daniel Leiszen

Questo metodo nasconde il calcolo nella presentazione della cella, che è facile da capire qui, ma diventa piuttosto complicato con tabelle complesse.
Marc Durdin

1
L'altro problema con questo è che non ha nemmeno il legame a due vie.
Paul Carlton

17

Puoi calcolare il totale all'interno di ng-repeatseguito:

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

Controlla il risultato qui: http://plnkr.co/edit/Gb8XiCf2RWiozFI3xWzp?p=preview

In caso di risultato dell'aggiornamento automatico: http://plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview (Grazie - VicJordan)


questo non funzionerà quando l'elenco è filtrato - tbodyviene inizializzato solo una volta, ma trogni volta che l'elenco viene filtrato, risultando in una somma errata
Zbynek

Puoi fare un esempio su plnkr o jsfiddle?
Huy Nguyen

Hmm, sì, non funziona nel filtro perché il filtro qui mostra / nascondi solo alla vista, non aggiorna$scope
Huy Nguyen

@HuyNguyen, ho modificato il codice sopra. Si prega di controllare qui: plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview . Quello che voglio è che se l'utente modifica la quantità, la quarta colonna (prezzo * quantità) dovrebbe essere aggiornata automaticamente. Potresti dare un'occhiata a questo. Grazie
Vikasdeep Singh

9

Questa è la mia soluzione

filtro personalizzato dolce e semplice:

(ma solo relativo alla semplice somma di valori, non alla somma del prodotto, ho creato un sumProductfiltro e l' ho aggiunto come modifica a questo 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

Questo è un sumProductfiltro, accetta qualsiasi numero di argomenti. Come argomento accetta il nome della proprietà dai dati di input e può gestire la proprietà annidata (annidamento contrassegnato da un punto:) property.nested;

  • Il passaggio di zero argomenti restituisce la lunghezza dei dati di input.
  • Il passaggio di un solo argomento restituisce la semplice somma dei valori di tali proprietà.
  • Il passaggio di più argomenti restituisce la somma dei prodotti dei valori delle proprietà passate (somma scalare delle proprietà).

ecco JS Fiddle e il codice

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


4

Soluzione semplice

Ecco una semplice soluzione. Nessun ulteriore per loop richiesto.

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

3

Un altro modo per risolvere questo problema, che si estende dalla risposta di Vaclav per risolvere questo particolare calcolo, ovvero un calcolo su ogni riga.

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

Per fare ciò con un calcolo, aggiungi semplicemente una funzione di calcolo al tuo ambito, ad es

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

Useresti {{ datas|total:calcItemTotal|currency }}nel tuo codice HTML. Questo ha il vantaggio di non essere chiamato per ogni digest, perché utilizza filtri e può essere utilizzato per totali semplici o complessi.

JSFiddle


3

Questo è un modo semplice per farlo con ng-repeat e ng-init per aggregare tutti i valori ed estendere il modello con una proprietà 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 direttiva ngInit chiama la funzione set total per ogni elemento. La funzione setTotals nel controller calcola il totale di ogni elemento. Utilizza anche le variabili di ambito invoiceCount e invoiceTotal per aggregare (sommare) la quantità e il totale per tutti gli articoli.

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

per maggiori informazioni e demo guarda questo link:

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


1
I link al tuo post sul blog che potrebbero non essere più collegati sono scoraggiati su StackOverlow. Inoltre, quando guardo la pagina ottengo un errore 502 Bad Gateway al centro della pagina. Rispondi alla domanda qui, non un collegamento a qualche altra parte.
Rick Glos

3

Preferisco soluzioni eleganti

Nel modello

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

Se stai usando ES2015 (noto anche come ES6)

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

2

È possibile utilizzare un filtro angolare personalizzato che richiede la somma dell'array di oggetti del set di dati e della chiave in ogni oggetto. Il filtro può quindi restituire la somma:

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

Quindi nella tua tabella per sommare una colonna puoi usare:

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

1

Puoi provare a utilizzare i servizi di angular js, ha funzionato per me ... dando i frammenti di codice di seguito

Codice controller:

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

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

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

Codice di servizio:

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

ecco la mia soluzione a questo problema:

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

copione

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

reduce verrà eseguito per ogni prodotto nell'array di prodotti. L'accumulatore è la quantità totale accumulata, currentValue è l'elemento corrente dell'array e lo 0 nell'ultimo è il valore iniziale


0

Ho approfondito un po 'la risposta di RajaShilpa. Puoi usare la sintassi come:

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

in modo da poter accedere all'oggetto figlio di un oggetto. Ecco il codice per il 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;
    }
});

0

Prendendo la risposta di Vaclav e rendendola più angolare:

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

Questo ti dà il vantaggio di accedere anche ai dati nidificati e di matrice:

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

0

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

0

La risposta di Huy Nguyen è quasi arrivata. Per farlo funzionare, aggiungi:

ng-repeat="_ in [ products ]"

... alla linea con ng-init. L'elenco ha sempre un singolo elemento, quindi Angular ripeterà il blocco esattamente una volta.

La demo di Zybnek che utilizza il filtro può essere fatta funzionare aggiungendo:

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

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


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

Dalla recensione: Benvenuto in Stack Overflow! Per favore, non rispondere solo con il codice sorgente. Prova a fornire una bella descrizione su come funziona la tua soluzione. Vedi: come scrivo una buona risposta? . Grazie
sɐunıɔ ןɐ qɐp

0

Questa è la mia soluzione

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

È necessario aggiungere un nome al controller in Controller as SomeNamemodo da poter memorizzare nella cache la variabile (è davvero necessario? Non ho familiarità con l'uso di $ parent quindi non lo so)

Quindi per ogni ripetizione, aggiungi ng-init"SomeName.SumVariable = ($first ? 0 : SomeName.SumVariable) + repeatValue"

$first per verificare che sia prima, quindi azzerato, altrimenti continuerebbe il valore aggregato

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


-2

Dopo aver letto tutte le risposte qui - come riassumere le informazioni raggruppate, ho deciso di saltare tutto e ho appena caricato una delle librerie javascript SQL. Sto usando alasql, sì, ci vogliono alcuni secondi in più sul tempo di caricamento ma risparmia un numero infinito di tempo nella codifica e nel debug, ora per raggruppare e sum () lo uso,

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

So che questo suona come un rant su angular / js, ma in realtà SQL lo ha risolto più di 30 anni fa e non dovremmo reinventarlo all'interno di un browser.


1
Questo è semplicemente orribile. Wow SMH - lascerò che altre persone votino. La mia bocca è spalancata con questa risposta .....
Tom Stickel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.