2

I have an array with persons who have scores. I display with the list of persons with their scores and a checkbox. When the user checks the checkbox, I total all scores that are checked.: http://jsfiddle.net/edwardtanguay/hwutbgv3/16

However, the totals are one check behind since it seems that when Angular executes the function contained in ng-click, it has not updated the value of ng-model.

How can I get Angular to update the value FIRST, and then execute the function?

<div ng-controller="MyCtrl">
    <ul>
        <li ng-repeat="person in persons | orderBy:'score'"><input type="checkbox" ng-model="person.isChecked" ng-click="updateTotals()"/> {{person.name}} = {{person.score}} ({{person.isChecked}}) 

        </li>
    </ul>
    <div>{{message}}</div>
    <div>total score of checked persons: {{total}}</div>
</div> 

var myApp = angular.module('myApp',[]);

function MyCtrl($scope, $window) {

    $scope.debug = '';
    $scope.total = 0;

    $scope.persons = [
        {score: 1, name: 'Jim', isChecked: true},
        {score: 2, name: 'Joe', isChecked: false},
        {score: 3, name: 'Angie', isChecked: true}
    ];

    $scope.updateTotals = function() {
        $scope.total = 0;
        for(var x=0; x < $scope.persons.length; x++) {
            var person = $scope.persons[x];
            if(person['isChecked']) {
                $scope.total += person['score'];   
            }
        }
    }

    $scope.updateTotals()
}
Edward Tanguay
  • 189,012
  • 314
  • 712
  • 1,047

2 Answers2

2

This happens because ng-click is trigger before the ng-model changes so you need to use ng-change instead of ng-click.

<li ng-repeat="person in persons | orderBy:'score'">
    <input type="checkbox" ng-model="person.isChecked" ng-change="updateTotals(person)"/>
    .....
</li>

here is a DEMO


Also you can get rid of the controller updateTotals() and manage it inside the view like

define total in controller

// using a object because we need this inside `ng-repeat` which create a child scope, otherview we need to call it as $parent.total in the view.
$scope.all = {
    total: 0
};

in html

<ul>
    <li ng-repeat="person in persons | orderBy:'score'" ng-init="person.isChecked ? (all.total = all.total+person.score) : return">
        <input type="checkbox" ng-model="person.isChecked" ng-change="person.isChecked ? (all.total = all.total+person.score) : (all.total = all.total-person.score)"/>
        {{person.name}} = {{person.score}} ({{person.isChecked}})
    </li>
</ul>

use ng-init to calculate the total when initial phase

...ng-init="person.isChecked ? (all.total = all.total+person.score) : return"...

add or deduct the score when changing the checkbox value

<input type="checkbox" ng-model="person.isChecked" ng-change="person.isChecked ? (all.total = all.total+person.score) : (all.total = all.total-person.score)"/>

here is the DEMO


If you feel like the html is bit messy then move the conditions and updating the total to the controller as

call updateTotal on initial phase and the checkbox change phase and pass the arguments (person & true/false to indicate the initial phase or not)

<li ng-repeat="person in persons | orderBy:'score'" ng-init="updateTotal(person, true)">
    <input type="checkbox" ng-model="person.isChecked" ng-change="updateTotal(person, false)"/>
    {{person.name}} = {{person.score}} ({{person.isChecked}})
</li> 

in controller

$scope.updateTotal = function(person, isInitPhase) {
    if (isInitPhase) {
        if (person.isChecked) {
            $scope.total += person.score;
        }
    } else {
        if (person.isChecked) {
          $scope.total += person.score;
        } else {
            $scope.total -= person.score;
        }
    }
}

here is the DEMO

I choose last one because I feel it lot cleaner.

Kalhan.Toress
  • 21,683
  • 8
  • 68
  • 92
0

Do not update view-data yourself, use data binding for that. Just rename your updateTotals-function to getTotal and make it return the calculated total:

$scope.getTotal = function() {
    var total = 0;

    for(var x=0; x < $scope.persons.length; x++) {
        var person = $scope.persons[x];
        if(person['isChecked']) {
            total += person['score'];   
        }
    }

    return total;
}

and change your template to show getTotal():

<div>total score of checked persons: {{getTotal()}}</div>

Now you can remove your ng-click on your checkbox and let angular do the work for you.

On a different note, I would recommend to look into controllerAs-syntax and to start using it, it has a number of different advantages, including readability and removing $scope from your code.

Community
  • 1
  • 1
LionC
  • 3,106
  • 1
  • 22
  • 31