solving $rootScope:infdig Infinite $digest Loop
So I finally figured out my own problem and thought I would answer it for others in case someone else may find this info useful.
The crux of the fix had to do with 2 concepts: angular promises and angular watches. By being aware of and applying the 2 concepts together the fix was actually pretty simple.
Everything you put on $scope is 'watched' including functions. Every time something watched changes $scope.$apply() runs again to apply the changes. If a scope function (eg: $scope.isAdmin()) changes its return value from one 'apply' to the next it will trigger another 'apply', until things stabilize and the return value isn't changing.
But in my code I was returning user.then(...) which just returns a new promise (which kept the apply cycle going on forever since the return value kept changing).
In my isAdmin() function I needed to defer its return value until the user actually loaded (any other return value would be meaningless). So I changed the code to check if the user async call had resolved by checking $scope.user and if so returning a valid isAdmin value. If $scope.user was still not defined I would just return the promise I already created.
I changed the $scope.isAdmin() to be:
$scope.isAdmin = function() {
if ($scope.user) {
return $scope.user.isAdmin;
}
return user;
};
This has the same effect as the original code without triggering an infinite apply cycle. Specifically, if the $scope.user has not resolved we still return a promise as before, by returning the user var. Note however that the user var is the same promise not a new one created by then() so the apply cycle stabilizes.
And just for completeness here is the updated jsfiddle:
lostdorje
Updated on June 06, 2022Comments
-
lostdorje almost 2 years
I get the basic idea of the infinite digest loop and how it happens, but I'm running into the problem. Here is a fiddle demonstrating my code and problem:
In the jsfiddle console you'll see the infinite digest loop.
Basically I have to make decisions on data that may not have loaded yet so I need to wait for the promise to resolve using then(). I have a promise called user. There are two different places in the code where I call then() on user.
- Just after I define it. I need to set a scope variable based on it.
- In another scope method, $scope.isAdmin()
For number 2, it might be asked why I just don't use $scope.user directly in the $scope.isAdmin() method. The problem is, it's possible for $scope.isAdmin() to be called before the async request for the user returns, in which case I need to 'block' before returning from $scope.isAdmin().
My question is, what about $scope.isAdmin() is making angular think that a 'watched' variable has changed and that the digest cycle needs to run again?
$scope.isAdmin() isn't actually changing anything.
Here is the stripped down code:
HTML:
<body ng-controller='myController'> <div ng-if='isAdmin()'>Hi! <strong>{{ user.username }}</strong> is an Admin!!!</div> <div ng-if='!isAdmin()'>Hi! <strong>{{ user.username }}</strong> is NOT an Admin!!!</div> </body>
And the JS:
angular.module('myApp', []) .factory('myService', function($q, $timeout) { return { getUser: function() { var deferred = $q.defer(); $timeout(function() { deferred.resolve({ username: 'me', isAdmin: true }); }, 2000); return deferred.promise; } }; }) .controller('myController', function($scope, $q, myService) { var getUserDeferred = $q.defer(); var user = getUserDeferred.promise; user.then(function(user) { $scope.user = user; return user; }); $scope.getUser = function() { return myService.getUser().then(function(user) { getUserDeferred.resolve(user); }); }; $scope.isAdmin = function() { return user.then(function(user) { return user.isAdmin; }); }; $scope.getUser(); });
-
keithl8041 about 8 yearsThanks for answering your own question - this was the really relevant bit for me which made me understand my issue:
But in my code I was [...] which just returns a new promise (which kept the apply cycle going on forever since the return value kept changing)