Search code examples
javascriptangularjsangularjs-scope

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


Solution

  • 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-time bindings (see the section on https://docs.angularjs.org/guide/expression titled One Time Bindings)

    So, you could do something like

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