Bengalaa Bengalaa - 2 months ago 7
AngularJS Question

How to run a function attached to an angular controller just once in a binding

I attached a function to the scope, and i wanted to run it in a binding, like this:

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

function MyCtrl($scope) {
var count = 0;

$scope.f = function () {
count++;
return count;
}
}


html...

<div ng-controller="MyCtrl">
{{f()}}
</div>


but when i run it, the function returns 11, as you can check on this fiddle: http://jsfiddle.net/h4w4yc6L/

it seems like the function is running several times, even though i only call it once on the html, because my console complains about

angular.js:13920
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!


please do be aware that i am really newbie on angular! i don't know why this is happening, so I will be really glad if you provide me with information about why this happens, how to avoid this, and what is the right way to achieve this...

thanks in advance :)

Answer

Angular works by binding a model to view through a scope. Every so often, it will run a digest cycle on a scope to update the associated view. During the digest cycle, (almost) all bindings within that scope are reevaluated at least once - and possibly multiple times, since the digest cycle repeats multiple times if it thinks it needs to (if values aren't 'stable'). I recommend reading https://docs.angularjs.org/guide/scope, specifically the section on scope lifecycle.

Since you bind directly to the function expression {{f()}} (see https://docs.angularjs.org/guide/expression), Angular will call the function every time it reevaluates the binding. Using the interpolation syntax {{}} with functions that change the state of the controller or model is not recommended, since you do not control how often they are evaluated

I should first note that if you want the function to re-evaluate only on some user input (eg. click), use the appropriate binding (ng-click), etc.

If you really want the function to be evaluated only once, you may want to evaluate it in the controller's constructor and store the resulting value. Eg.

function MyCtrl($scope) {
    var count = 0;

    $scope.f = function () {
      count++;
      return count;
    }

    $scope.g = f();
}

<div ng-controller="MyCtrl">
   {{g}}
</div>

Or you can use ng-init (See https://docs.angularjs.org/api/ng/directive/ngInit, and note the warning at the top)

So, with ng-init, you might get

<div ng-controller="MyCtrl" ng-init="foo = f()">
   {{foo}}
</div>

This is not the recommended pattern though.

Another option is to use one-way bindings (see the section on https://docs.angularjs.org/guide/expression titled One Way Bindings)

So, you could do something like

<div ng-controller="MyCtrl">
   {{::f()}}
</div>