Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function Called Multiple Times in AngularJS Repeat Section

I'm running into an issue where I want to bind to the output of a function inside of an ng-repeat loop. I'm finding that the function is being called twice per item rather than once as I'd expect. Here's the ng-repeat section (notice the calcRowTotal() call at the end):

<tr ng-repeat="row in timesheetRows">
    <td>
        <select ng-model="row.categoryID">
            <option ng-repeat="category in categories" value="{{category.id}}">
                {{category.title}}
            </option>
        </select>
    </td>
    <td ng-repeat="day in row.dayValues">
        <input type="text" ng-model="day.hours" />
    </td>
    <td>{{calcRowTotal($index, row)}}</td>
</tr>

The calcRowTotal() function is shown next:

$scope.calcRowTotal = function (index, row) {
    console.log('calcRowTotal - Index: ' + index);
    var total = 0;
    for (var i = 0; i < row.dayValues.length; i++) {
        var num = parseFloat(row.dayValues[i].hours);
        if (isNaN(num)) {
            num = 0;
            //this.dayValues[i].hours = 0;
        }
        total += num;
    }
    //updateDayTotals();
    return total;
}

An example of one of the items being iterated through is shown next:

{
    categoryID: 2,
    dayValues: [
                    { day: $scope.days[0], hours: 5 },
                    { day: $scope.days[1], hours: 0 },
                    { day: $scope.days[2], hours: 3 },
                    { day: $scope.days[3], hours: 0 },
                    { day: $scope.days[4], hours: 2 },
                    { day: $scope.days[5], hours: 5 },
                    { day: $scope.days[6], hours: 8 }
    ]
}

I'm seeing the following in the console (two items are currently in the collection I'm looping through):

calcRowTotal - Index: 0 
calcRowTotal - Index: 1 
calcRowTotal - Index: 0 
calcRowTotal - Index: 1 

I could certainly make a "rowTotal" property but would prefer to bind to "live" data provided by the function shown above. Hopefully the duplication is something simple I'm missing so I appreciate any feedback on why I'm seeing the duplication. As a side note, as data in one of the textboxes changes I need to update the row totals as well so it may be I need a different approach. Interested in understanding this particular situation first though....definitely don't want the duplication because there could be a lot of rows potentially.

Here's an example: http://jsfiddle.net/dwahlin/Y7XbY/2/

like image 518
Dan Wahlin Avatar asked Feb 20 '13 18:02

Dan Wahlin


People also ask

How to call a function repeatedly every 5 seconds in JavaScript?

- GeeksforGeeks How to call a function repeatedly every 5 seconds in JavaScript ? The setInterval () method in JavaScript can be used to perform periodic evaluation of expression or call a JavaScript function. setInterval (function, milliseconds, param1, param2, ...) function: This parameter holds the function name which to be called periodically.

Why does Ng-class run multiple times?

When Angular’s digest cycle runs, every watcher is asked to update its state. In the case of ng-class, it will re-run the function bound to it, to see if anything needs to change. This is why your controller function runs multiple times, and it’ll run again each time something changes on the page.

What is ng-repeat directive in angular?

The ng-repeat directive repeats a set of HTML, a given number of times. The set of HTML will be repeated once per item in a collection. The collection must be an array or an object.

How does the Digest cycle work in angular?

When the digest cycle runs, it effectively redraws everything that might have changed on the page. Angular uses some tricks to find “everything that might have changed”, and the main technique is watchers. These watchers are created automatically when you use directives like ng-if and ng-class, and when you use bindings like { { yourBindingHere }}.


1 Answers

It's because you're binding to a function expression here:

<td>{{calcRowTotal($index, row)}}</td>

What that does it force that function to be reevaluated on every item, on every digest. What you'll want to do to prevent that is pre-calculate that value and put it in your array to begin with.

One way to do that is to set up a watch on your array:

$scope.$watch('timesheetRows', function(rows) {
   for(var i = 0; i < value.length; i++) {
     var row = rows[i];
     row.rowTotal = $scope.calcRowTotal(row, i);
   }
}, true);

Then all you have to do is bind to that new value:

<td>{{row.rowTotal}}</td>
like image 122
Ben Lesh Avatar answered Nov 07 '22 22:11

Ben Lesh