Obliczanie sumy powtarzających się elementów w AngularJS ng-repeat


107

Poniższy skrypt wyświetla koszyk sklepu przy użyciu ng-repeat. Dla każdego elementu w tablicy pokazuje nazwę pozycji, jej kwotę i sumę częściową (product.price * product.quantity ).

Jaki jest najprostszy sposób obliczenia łącznej ceny powtarzanych elementów?

<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



Dlaczego nie używasz tfoot-tag?
Pascal

Odpowiedzi:


147

W szablonie

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

W kontrolerze

$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
jedną wadą jest to, że dwukrotnie iteruje kolekcję. jest to w porządku w przypadku małych kolekcji, ale co, jeśli kolekcja jest dość duża? wygląda na to, że w ng-repeat powinien istnieć sposób na uzyskanie sumy bieżącej na danym polu obiektu.
icfantv

2
@Pascamel Sprawdź moją odpowiedź ( stackoverflow.com/questions/22731145/ ... ) Myślę, że ten działa z filtrem o to, o co pytasz
Rajamohan Anguchamy

dokładnie to, czego szukałem, kiedy trafiłem na to pytanie, dzięki za ostrzeżenia @RajaShilpa!
Pascamel

2
Głównym problemem związanym z tym rozwiązaniem jest to, że suma będzie obliczana ponownie przy każdym podsumowaniu, ponieważ jest to wywołanie funkcji.
Marc Durdin

@icfantv w jaki sposób iteruje kolekcję dwukrotnie?
Crhistian Ramirez,

58

Działa to również zarówno na filtrze, jak i na liście normalnej. W pierwszej kolejności należy utworzyć nowy filtr dla sumy wszystkich wartości z listy, a także podane rozwiązanie dla sumy ilości całkowitej. W szczegółowym kodzie sprawdź link Fiddler .

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>

sprawdź ten link Fiddle


Hej, otrzymuję 'undefined'podczas używania resultValue, ale jeśli używam items, działa dobrze, jakieś pomysły .. ??
Salal Aslam

Gdzie najpierw sprawdzasz następujący kod „resultValue = (items | filter: {'details': searchFilter})”, ponieważ wszystkie wartości filtru są przechowywane w tej zmiennej „resultValue”. myślę, że pomyliłeś się z tym {} lub () z tym, sprawdź jeszcze raz.
Rajamohan Anguchamy

Jeśli itemsgo użyję, nie zadziała z filtrami, pomóż!
Salal Aslam

mój kod jest taki ng-repeat="campaign in filteredCampaigns=(campaigns | filter:{'name':q})"i{{ filteredCampaigns | campaignTotal: 'totalCommission' | number: 2 }}
Salal Aslam

tak, ponieważ pozycje nie są jeszcze filtrowane, po wykonaniu filtra wynik musi być przechowywany w innym modelu i powinien używać tylko tego modelu. W mojej próbce użyłem modelu „resultValue”.
Rajamohan Anguchamy

41

Zdając sobie sprawę z tego odpowiedział dawno temu, ale chciałem opublikować inne podejście, które nie zostało przedstawione ...

Służy ng-initdo obliczania sumy. W ten sposób nie musisz iterować w kodzie HTML i iterować w kontrolerze. W tym scenariuszu myślę, że jest to czystsze / prostsze rozwiązanie. (Jeśli logika liczenia byłaby bardziej złożona, zdecydowanie zalecałbym przeniesienie logiki do kontrolera lub usługi, w zależności od potrzeb).

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

Oczywiście w kontrolerze po prostu zdefiniuj / zainicjuj swoje Totalpole:

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

4
To zdecydowanie najbardziej kanciasty sposób. Prosty, czytelny i deklaratywny. Tak więc logika, którą reprezentuje, pozostaje tam, gdzie należy.
Daniel Leiszen

Ta metoda ukrywa obliczenia w prezentacji komórki, która jest tutaj łatwa do zrozumienia, ale robi się dość skomplikowana w przypadku złożonych tabel.
Marc Durdin

1
Inną kwestią jest to, że nie ma też dwustronnego wiązania.
Paul Carlton

17

Możesz obliczyć sumę wewnątrz ng-repeatfollow:

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

Sprawdź wynik tutaj: http://plnkr.co/edit/Gb8XiCf2RWiozFI3xWzp?p=preview

W przypadku wyniku automatycznej aktualizacji: http://plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview (dzięki - VicJordan)


to nie zadziała, gdy lista jest filtrowana - tbodyjest inicjowana tylko raz, ale za trkażdym razem, gdy lista jest filtrowana, co skutkuje niepoprawną sumą
Zbynek

Czy możesz zrobić przykład na plnkr lub jsfiddle?
Huy Nguyen

Hmm, tak, to nie działa w filtrze, ponieważ filtr tutaj po prostu pokaż / ukryj na widoku, a nie aktualizuj$scope
Huy Nguyen

@HuyNguyen, zredagowałem twój powyższy kod. Sprawdź tutaj: plnkr.co/edit/QSxYbgjDjkuSH2s5JBPf?p=preview . Tutaj chcę, aby użytkownik zmienił ilość, a czwarta kolumna (cena * ilość) powinna zostać zaktualizowana automatycznie. Czy mógłbyś rzucić okiem na to. Dzięki
Vikasdeep Singh

9

To jest moje rozwiązanie

słodki i prosty filtr niestandardowy:

(ale tylko w odniesieniu do prostej sumy wartości, a nie iloczynu sumarycznego, stworzyłem sumProductfiltr i dołączyłem go jako edit do tego postu).

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

EDYCJA: sumProduct

To jest sumProductfiltr, akceptuje dowolną liczbę argumentów. Jako argument przyjmuje nazwę właściwości z danych wejściowych i może obsługiwać zagnieżdżoną właściwość (zagnieżdżenie zaznaczone kropką:) property.nested;

  • Przekazanie argumentu zerowego zwraca długość danych wejściowych.
  • Przekazanie tylko jednego argumentu zwraca prostą sumę wartości tych właściwości.
  • Przekazanie większej liczby argumentów zwraca sumę iloczynów wartości przekazanych właściwości (suma skalarna właściwości).

oto JS Fiddle i kod

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

Proste rozwiązanie

Oto proste rozwiązanie. Nie jest wymagana dodatkowa pętla.

Część 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>

Część skryptowa

 $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

Inny sposób rozwiązania tego problemu, począwszy od odpowiedzi Wacława na rozwiązanie tego konkretnego obliczenia - tj. Obliczenia w każdym wierszu.

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

Aby to zrobić z obliczeniem, po prostu dodaj funkcję obliczeniową do swojego zakresu, np

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

Użyłbyś {{ datas|total:calcItemTotal|currency }} w swoim kodzie HTML. Ma to tę zaletę, że nie jest wywoływane przy każdym podsumowaniu, ponieważ używa filtrów i może być używane do prostych lub złożonych podsumowań.

JSFiddle


3

Jest to prosty sposób na zrobienie tego za pomocą ng-repeat i ng-init, aby zagregować wszystkie wartości i rozszerzyć model o właściwość 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>

Dyrektywa ngInit wywołuje funkcję set total dla każdego elementu. Funkcja setTotals w kontrolerze oblicza sumę każdej pozycji. Wykorzystuje również zmienne zakresu faktura_konto i suma_ faktury do agregowania (sumowania) ilości i sumy wszystkich towarów.

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

więcej informacji i demo znajdziesz pod tym linkiem:

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


1
Linki do twojego posta na blogu, które mogą stracić łączność, są odradzane w StackOverlow. Ponadto, kiedy patrzę na stronę, widzę błąd 502 Bad Gateway na środku strony. Odpowiedz na pytanie tutaj, bez linku do innego miejsca.
Rick Glos

3

Preferuję eleganckie rozwiązania

W szablonie

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

W kontrolerze

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

Jeśli używasz ES2015 (aka ES6)

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

2

Możesz użyć niestandardowego filtru kątowego, który pobiera tablicę obiektów zestawu danych i klucz w każdym obiekcie do zsumowania. Filtr może następnie zwrócić 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;
        };
    })

Następnie w swojej tabeli, aby zsumować kolumnę, której możesz użyć:

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

1

Możesz spróbować skorzystać z usług angular js, zadziałało to dla mnie… przekazując fragmenty kodu poniżej

Kod kontrolera:

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

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

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

Kod serwisowy:

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

oto moje rozwiązanie tego problemu:

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

scenariusz

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

Redukcja zostanie wykonana dla każdego produktu w tablicy products. Akumulator to całkowita skumulowana kwota, currentValue to bieżący element tablicy, a 0 w ostatnim jest wartością początkową


0

Rozszerzyłem nieco odpowiedź RajaShilpy. Możesz użyć składni takiej jak:

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

abyś mógł uzyskać dostęp do obiektu potomnego obiektu. Oto kod filtru:

.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

Przyjmując odpowiedź Vaclava i czyniąc ją bardziej podobną do Angulara:

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

Daje to korzyść z uzyskania nawet dostępu do danych zagnieżdżonych i tablicowych:

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

0

W html

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

w 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

Odpowiedź Huy Nguyena jest już prawie gotowa. Aby to zadziałało, dodaj:

ng-repeat="_ in [ products ]"

... do linii z ng-init. Lista zawsze zawiera jedną pozycję, więc Angular powtórzy blok dokładnie raz.

Demo Zybnek używające filtrowania można uruchomić, dodając:

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

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

Z recenzji: Welcome to Stack Overflow! Nie odpowiadaj tylko za pomocą kodu źródłowego. Postaraj się przedstawić miły opis tego, jak działa Twoje rozwiązanie. Zobacz: Jak napisać dobrą odpowiedź? . Dzięki
sɐunıɔ ןɐ qɐp

0

To jest moje rozwiązanie

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

Wymaga dodania nazwy do kontrolera jako Controller as SomeName , abyśmy mogli tam buforować zmienną (czy to naprawdę wymaga? Nie znam się na używaniu $ parent, więc nie wiem)

Następnie dla każdego powtórzenia dodaj ng-init"SomeName.SumVariable = ($first ? 0 : SomeName.SumVariable) + repeatValue"

$first aby sprawdzić, czy jest najpierw, a następnie resetuje się do zera, w przeciwnym razie kontynuowałaby wartość zagregowaną

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


-2

Po przeczytaniu wszystkich odpowiedzi tutaj - jak podsumować zgrupowane informacje, postanowiłem pominąć to wszystko i po prostu załadowałem jedną z bibliotek javascript SQL. Używam alasql, tak, ładowanie trwa kilka sekund dłużej, ale oszczędza niezliczoną ilość czasu na kodowaniu i debugowaniu, teraz grupowanie i sumowanie () Po prostu używam,

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

Wiem, że brzmi to trochę jak rant na temat angular / js, ale tak naprawdę SQL rozwiązał ten problem ponad 30 lat temu i nie powinniśmy być zmuszeni do ponownego wymyślania tego w przeglądarce.


1
To jest po prostu okropne. Po prostu wow SMH - pozwolę innym głosować. Moje usta są szeroko otwarte z tą odpowiedzią .....
Tom Stickel,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.