12

I have an angular application that displays the value returned by a controller method through a simple expression binding:

<div>{{getValue()}}</div>

If the method in question just returns a value, the method is called twice, and that is strange enough:

$scope.getValue = function(){
  return 'some value';
}

But if the method does some asynchronous work such as getting a file from the server, the code goes into an infinite loop:

$scope.getValueAsync = function(){
  $http.get('myfile.html')
    .success(function (data, status, headers, config) {
      return 'some async value';
    });

  return 'file not found'; // same value returned every time but $digest cycle still loops
}

I'm new to Angular so have probably missed something basic here, but can someone please explain what is going on?

Plunker

Here's a plunker to play with http://plnkr.co/7BriYDbdVJvIoIigQcTU

Stewie
  • 60,366
  • 20
  • 146
  • 113
Paul Taylor
  • 5,651
  • 5
  • 44
  • 68
  • Yes, I think that probably explains it, thanks. However, the getValueAsync method in my example always returns the same value, so I can't see why Angular would see fit to continue calling it indefinitely. – Paul Taylor Jul 04 '13 at 10:15

2 Answers2

17

Even though your async function returns the same exact string every time, the $digest cycle is fired in loops because your function also makes an ajax call with $http service.

$http service triggers $rootScope.$apply() when requests are completed and since $apply triggers a $digest cycle it makes your view expression to be re-evaluated, which in return causes your async function to be called again, and so on...

app.controller('MainCtrl', function($scope, $http) {

  $scope.getValue = function(){
    return 'some value';
  }

  $scope.getValueAsync = function(){
    $http.get('myfile.html')
      .success(function (data, status, headers, config) {
        return 'some async value';
      });

    return 'file not found';
  }
});
<div>{{getValueAsync()}}</div>

Moral of the story: If you use functions in expressions, make sure your functions don't affect something outside of them that would trigger a $digest loop, and make sure your functions always return the same output given the same input.

Stewie
  • 60,366
  • 20
  • 146
  • 113
  • Thanks. This seems fairly esoteric - even unintuitive - behaviour though I can perhaps see why it's necessary given Angular's claim to fame for two-way binding. – Paul Taylor Jul 04 '13 at 11:49
  • One other comment, the guideline you give effectively means that views should never be bound to functions if the functions use promises internally. That really does merit emphasis in the Angular docs. – Paul Taylor Jul 04 '13 at 11:51
  • 1
    In fact, I'd go further! $http.get() is a classic function and should not have side effects such as this. -1 for Angular. – Paul Taylor Jul 04 '13 at 13:01
  • 3
    Given the great and elegant power that AngularJS provides and given the weaknesses of the underlying JS language, I'd say one must give every credit to the Framework and accept few of it's shortcomings. Either way, it's quite easy to circumvent your specific problem by having the $http to execute when controller is loaded and assigning the results to a $scope variable. – Stewie Jul 04 '13 at 13:07
  • Fair comment @Stewie. Nevertheless, you can waste a lot of time on details like this. – Paul Taylor Jul 04 '13 at 13:10
  • @Stewie - Thanks for explaining this. Used this answer to resolve (hopefully) a Github issue :) https://github.com/mgonto/restangular/issues/293 – pankaj28843 Sep 07 '13 at 06:54
  • ummmm, how do we prevent the digest cycle from happening upon making an $http request...? I don't see how this answer solves a/the problem? – Alexander Mills Mar 08 '17 at 03:32
  • @Stewie what If I need to update a parent controller value from a child directive? I'm doing this by passing through the function to the child controller, calling it and passing back the updated value - http://stackoverflow.com/questions/42687088/passing-function-as-two-way-binding-re-triggers-digest-infinite-loop – user3871 Mar 09 '17 at 05:01
0

I met the same problem as you, to fix the problem we can cache the results of the function. For this purpose I prefer to use Lo-Dash’s memoize function. I create a demo to show you how i managed to fix this problem.The following link contains the demo:http://plnkr.co/edit/KBmk4J2ZCt0SsmZlnKZi?p=preview

Wekerle Tibor
  • 457
  • 2
  • 7
  • 19