Angular Infinite Digest Loop with promise.then() - angularjs

The problem: Using a this.promise.then(function(){}) function inside a controller method seems to create a $rootScope:infDig loop although the returned value of that method does not change. It's worth noting that the infinite digest loop is gone when removing the .then() line (see code).
The code:
{{ getSelectedProjects() }} in the view and the following method in the attached controller
var vm = this;
vm.getSelectedProjects = function() {
if (angular.isUndefined(vm.hello)) {
vm.hello = "hello"
vm.promise = $q.when("hello i am a resolved promise")
}
vm.promise //In my original code, this promise is returned by a service (MyService.getPromise()). The resolved value of the returned promise may change which is why the service needs to be called at each digest cycle.
.then(function (ans) {
vm.hello = ans;
}); //remove .then(...) function and there is no infinite digest loop anymore
return vm.hello;
}
The question: Why is this creating an infinite digest loop although the returned value does not change ? How can I avoid it (I badly need the result of that promise)?.
My shaky thoughts: I guess it has to do with the fact .then() each time returns a new promise. The documentation does not state it, but if that promise is each time attached to the controller object (vm), this might be considered as a state change of the controller and trigger a new digest loop, which re-evaluates vm.getSelectedProjects() and again attaches a new promise to the controller, etc.
EDIT
My hypothesis in "shaky thoughts" does not seem fully correct or complete, as the problem persists with this code:
vm.getSelectedProjects = function() {
if (angular.isUndefined(vm.hello)) {
vm.hello = "hello"
}
$q.when("hello i am a resolved promise")//this object is not attached to the controller object
.then(function () {});
return vm.hello;
}

Based on the problems encountered and the comments we created coding guidelines for working with promises in our project. Found it interesting to share it as I haven't seen this documented anywhere.
Coding guideline: Working with promises
Use of promises
Promises are amazing objects, however a wrong use can create headaches. Therefore we have a few guidelines :
Do not use promises in views
Do not use promises in controllers, except in functions called by event handlers (click, init, etc.), the controller construction function (=executed only once) or in animations
If a promise result is used in a view, the model (or the manager) should contain a property that is empty by default and set to the resolved value when the promise is resolved. The controller just returns that value.
Implementation
This is the kind of structure that should be in the model, manager, or other service, as close as possible to the data source. NOT in controllers :
var myApiMethod = function() {
//We call "caching" the fact that this method is executed only if $scope.item is undefined.
If (angular.isUndefined($scope.item) {
$scope.item = {}
$http(xxx).then(function(value){
$scope.item = value;
});
return $scope.item
}
}
The controller then just returns the answer of myApiMethod. You might need to check if 'myService.myApiMethod() != {}'.
In the service we can define a caching behaviour (ex: retrieve data from backend every 2 hours, at a certain event clear the cache, etc. ). As long as there is only one single source of truth (coming from the service in this case, not from the controller) concerning the object to be returned you can easilly play around with this.
Argumentation
*Why not to use promises in views or in controller methods called by expression evaluations (e.g., {{ vm.getMyApiMethodResult() }} *:
Including promises in views is a feature that has been desactivated in Angular 1.x
If myApiMethod is put in the controller without the caching and then used in an expression evaluation (e.g., {{ myCtrl.getMyApiMethod() }} ), it will create an infinite digest cycle, because the resolution of the promise will trigger a new digest cycle, that will again call the vm.getMyApiMethod() function, etc. (more info in this SO answer )
Why not put myApiMethod in t he controller with the caching (like implementation above) to make sure myApiMethod is called only once and avoid the infinite digest loop? Three reasons:
if the response of myApiMethod() changes into a new object, $scope.item will not be updated.
Another drawback of this (that we used a lot in the past, you might still see wrong implementations in the code) is that it creates a lot of duplicate code as we need to duplicate this quite heavy code structure (that can become much more complex when several promises are involved) in all controllers calling myApiMethod()
If you "cache" in the controller, you might end up with different controllers returning different answers although they both return the answer from the same service 'myApiMethod' but at different moments in time.
Remember : This guideline only applies to controller functions called in expression evaluations. You might need promises in controller functions called by event handlers (click, etc.) and that's fine because these functions are not called at each of angular's digest cycles.

Related

Can I tell why Angular 1 is calling a rootScope function on every digest

I am having an issue where I have a function like this...
$rootScope.canNavigate = function(stateName) {
return !stateName || Authentication.canNavigate.call(Authentication, $state.get(stateName));
};
The problem is this function gets called continuously. The stack trace is different every time but has one thing in common, it is always coming from $apply. I have commented out all of the watches that are using this function and it is still happening. Does anyone know why this is happening? I cannot seem to reproduce in a plunker.
Asiel probably has the right answer depending on your situation, but as to how to prevent it, you can either use the bind once expression as Asiel suggests or, if that's not possible for some reason, you could also save the results so you're not calling that service every time.
var canNavigateCache = {};
$rootScope.canNavigate = function(stateName) {
return !stateName
|| canNavigateCache[stateName]
|| canNavigateCache[stateName] =
Authentication.canNavigate.call(Authentication, $state.get(stateName));
};
This also gives you the ability to clear the cache if you need to by just resetting the cache object (canNavigateCache = {})
If there is a call of this function on any of your html, this function will be called every time angular executes a digest cycle in order to do dirty checking (See this question on SO in order to get some kind of reference about this)... except you use the bind once expression (See syntax on this article and this on SO)
It is called from $apply because
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
but $apply is (also) used by the own framework for making a digest cycle.

angularjs: $apply(fn) better than $apply()

I am reading
http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/
directive link function:
element.bind('click', function() {
scope.foo++;
scope.bar++;
scope.$apply();
});
a better way for using $apply:
element.bind('click', function() {
scope.$apply(function(){
scope.foo++;
scope.bar++;
});
});
What’s the difference? The difference is that in the first version, we are updating the values outside the angular context so if that throws an error, Angular will never know. Obviously in this tiny toy example it won’t make much difference, but imagine that we have an alert box to show errors to our users and we have a 3rd party library that does a network call and it fails. If we don’t wrap it inside an $apply, Angular will never know about the failure and the alert box won’t be there.
Confusion:
Why angular need to know error, i just need to show it for users. for example, there is an ajax request in link fn of directive, I just need to tell what happened if fails.
TAngular $scope has a function called $apply() which takes a function as an argument. AngularJS says that it will know about model mutation only if that mutation is done inside $apply(). So you simply need to put the code that changes models inside a function and call $scope.apply(), passing that function as an argument. After the $apply() function call ends, AngularJS knows that some model changes might have occurred. It then starts a digest cycle by calling another function —- $rootScope.$digest() — which propagates to all child scopes. In the digest cycle watchers are called to check if the model value has changed. if a value has changed, the corresponding listener function then gets called. Now it’s upto the listener how it handles the model changes.
The Ajax call through Angular buildin $http the model mutation code is implicitly wrapped withing $apply() call, so you don’t need any additional steps.

AngularJS : $q -> deferred API order of things (lifecycle) AND who invokes digest?

The $q service is very powerful in angularjs and make our life easier with asynchronous code.
I am new to angular but using deferred API is not very new to me. I must say that I completely ok with the How to use part of documentation + there are very useful links for that within the docs + I checked out the source either.
My question is more about the under the hood parts of deferred and promise API objects in angular. What are the exact phases in their life cycles and how are they interacts with rootScope.Scope(s). My assumptions are that when the promise resolves - it invokes the digest loop ??? yes / no ?
Can one provide a detailed answer with specific respect to the following list of aspects:
What is the order of things that happen in each of your described
steps / phases
When new deferred object with a new promise instance created - who aware of it / is it
important ?
How exactly the scope updated when promise object being resolved? Do i have to update it manually inside the callback or the digest will be automatically invoked and update the rootScope like declared here
mention at least one approach of updating the scope from within the promise callback
I assume there a lot of other useful aspects, feel free to provide them all.
I will appreciate and accept the most detailed answer, with as much as possible references to docs or source (that i couldn't find by myself). I can't find any previously discussion with this topic, if there already was - please post links.
ps: +1 for any one that will help by suggesting a better title for this question, please add your suggestions in a comment.
Cheers!
Promises have three states
Pending - this is how promises start.
Fulfilled - this is what happens when you resolve a deferred, or when the return value from .then fulfills, and it generally analogous to a standard return value.
Rejected - This is what happens when you reject a deferred, when you throw from a .then handler or when you return a promise that unwraps to a rejection*, it is generally analogous to a standard exception thrown.
In Angular, promises resolve asynchronously and provide their guarantees by resolving via $rootScope.$evalAsync(callback); (taken from here).
Since it is run via $evalAsync we know that at least one digest cycle will happen after the promise resolves (normally), since it will schedule a new digest if one is not in progress.
This is also why for example when you want to unit test promise code in Angular, you need to run a digest loop (generally, on rootScope via $rootScope.digest()) since $evalAsync execution is part of the digest loop.
Ok, enough talk, show me the code:
Note: This shows the code paths from Angular 1.2, the code paths in Angular 1.x are all similar but in 1.3+ $q has been refactored to use prototypical inheritance so this answer is not accurate in code (but is in spirit) for those versions.
1) When $q is created it does this:
this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
return qFactory(function(callback) {
$rootScope.$evalAsync(callback);
}, $exceptionHandler);
}];
Which in turn, does:
function qFactory(nextTick, exceptionHandler) {
And only resolves on nextTick passed as $evalAsync inside resolve and notify:
resolve: function(val) {
if (pending) {
var callbacks = pending;
pending = undefined;
value = ref(val);
if (callbacks.length) {
nextTick(function() {
var callback;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
callback = callbacks[i];
value.then(callback[0], callback[1], callback[2]);
}
});
}
}
},
On the root scope, $evalAsync is defined as:
$evalAsync: function(expr) {
// if we are outside of an $digest loop and this is the first time we are scheduling async
// task also schedule async auto-flush
if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
$browser.defer(function() {
if ($rootScope.$$asyncQueue.length) {
$rootScope.$digest();
}
});
}
this.$$asyncQueue.push({scope: this, expression: expr});
},
$$postDigest : function(fn) {
this.$$postDigestQueue.push(fn);
},
Which, as you can see indeed schedules a digest if we are not in one and no digest has previously been scheduled. Then it pushes the function to the $$asyncQueue.
In turn inside $digest (during a cycle, and before testing the watchers):
asyncQueue = this.$$asyncQueue,
...
while(asyncQueue.length) {
try {
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
lastDirtyWatch = null;
}
So, as we can see, it runs on the $$asyncQueue until it's empty, executing the code in your promise.
So, as we can see, updating the scope is simply assigning to it, a digest will run if it's not already running, and if it is, the code inside the promise, run on $evalAsync is called before the watchers are run. So a simple:
myPromise().then(function(result){
$scope.someName = result;
});
Suffices, keeping it simple.
* note angular distinguishes throws from rejections - throws are logged by default and rejections have to be logged explicitly
when the promise resolves - it invokes the digest loop ?
Yes. You can test this by a simple template:
{{state}}
and controller code that changes the $scope.state variable after a delay:
$scope.state = 'Pending';
var d = $q.defer();
d.promise.then(function() {
$scope.state = 'Resolved, and digest cycle must have run';
});
$window.setTimeout(function() {
d.resolve();
}, 1000);
You can see this at http://plnkr.co/edit/fIfHYz9EYK14A5OS6NLd?p=preview. After a second, the text in the HTML shows Resolved, and digest cycle must have run. The call to setTimeout rather than $timeout is deliberate, to ensure that it must be resolve which ends up starting the digest loop.
This is confirmed by looking in the source: resolve calls its callbacks via nextTick, which is a function that passed the callbacks to $rootScope.$evalAsync, which according to the $evalAsync docs:
at least one $digest cycle will be performed after expression execution
Those same docs also say:
Note: if this function is called outside of a $digest cycle, a new $digest cycle will be scheduled
so whether the stack is already in the $digest loop can change the exact order of events.
'1. What is the order of things that happen in each of your described steps / phases
Going into the previous example in detail:
var d = $q.defer();
The deferred object's promise is in a pending state. At this point, virtually nothing has happened, you just have a deferred object with resolve, reject, notifiy and promise properties. Nothing has used or affected the $digest loop
d.promise.then(function() {
$scope.state = 'Resolved, and digest cycle must have run';
});
The promise is still in a pending state, but with a then success callback registered. Again, nothing has used or affected the $digest loop or any scope.
$window.setTimeout(function() {
d.resolve();
}, 1000);
After 1 second, d.resolve will be called. This passes the callback defined in Step 2 above to $evalAsync (via nextTick).
$evalAsync will call the callback
$evalAsync will then ensure one $digest cycle will be called.
'2. When new deferred object with a new promise instance created - who aware of it / is it important ?
Only the caller of $q.defer(). Nothing happens with respect to any scope until resolved is called (or indeed, reject or notify).
'3. How exactly the scope updated promise object being resolved? Do i have to update it manually inside the callback or the digest will be automatically invoked and update the rootScope like declared here
As mentioned, the $digest loop will be automatically started by calling resolve (if it's not in it already).
'4. mention at least one approach of updating the scope from within the promise callback
The example above gives this.
'5. I assume there a lot of other useful aspects, feel free to provide them all.
Not that I can think of!

What is $$phase in AngularJS?

I found this code snippet which is part of a angular directive someone wrote for bootstrap modal.
//Update the visible value when the dialog is closed
//through UI actions (Ok, cancel, etc.)
element.bind("hide.bs.modal", function () {
scope.modalVisible = false;
if (!scope.$$phase && !scope.$root.$$phase)
scope.$apply();
});
I understood that this part is for the latter half of two way binding we bind to hide.bs.modal event and update modal when UI changes.
I just wanted to know why is the person checking $$phase for scope and rootScope before calling apply ?
Can't we straightaway call apply ?
What is $$phase here?
I tried searching a lot, couldn't find any good explanation.
EDIT:
I found where I saw the example:
Simple Angular Directive for Bootstrap Modal
$$phase is a flag set while angular is in a $digest cycle.
Sometimes (in rare cases), you want to check $$phase on the scope before doing an $apply. An error occurs if you try to $apply during a $digest:
Error: $apply already in progress
Davin is totally correct that it's a flag that angular sets during the digest cycle.
But don't use it in your code.
I recently got a chance to ask Misko (angular author) about $$phase, and he said to never use it; it's an internal implementation of the digest cycle, and it's not future safe.
To make sure your code continues to work in the future, he suggested wrapping whatever you want to "safe apply" inside of a $timeout
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
})
This comes up a lot when you have callbacks or other things that might resolve during the digest cycle (but don't always)
Here's an example snippet from when I was dealing with one of google's libraries: (The rest of the service this was from has been left off.)
window.gapi.client.load('oauth2', 'v2', function() {
var request = window.gapi.client.oauth2.userinfo.get();
request.execute(function(response) {
// This happens outside of angular land, so wrap it in a timeout
// with an implied apply and blammo, we're in action.
$timeout(function() {
if(typeof(response['error']) !== 'undefined'){
// If the google api sent us an error, reject the promise.
deferred.reject(response);
}else{
// Resolve the promise with the whole response if ok.
deferred.resolve(response);
}
});
});
});
Note that the delay argument for $timeout is optional and will default to 0 if left unset ($timeout calls $browser.defer which defaults to 0 if delay isn't set)
A little non-intuitive, but that's the answer from the guys writing Angular, so it's good enough for me!
In that example, the element binding will get executed from a non-angular event. In most cases, it is safe to just call $apply() without checking the phase.
If you look at the rest of the code, however, there is a $scope function called showModal(). This function calls into the non-angular code which will likely cause a "hide.bs.modal" event to fire. If the event fires via this route, then the call stack is within a $digest.
So, this event does fall within the rare case of a function that will get called from both angular-managed code AND non-angular code. Checking the $$phase in this case is necessary because you don't know how the event originated. If the $$phase is set to something, then the digest loop will finish to completion and $apply() doesn't need to be called.
This pattern is often referred to as "safe apply".
my understanding is it is good to use when digesting or applying the scope. If it is truthy it means that there is currently a $digest or $apply phase in progress. If you are getting related errors you can do $scope.$$phase || $scope.digest(); which will only digest if $scope.$$pahse is falsy.
You can use $scope.$evalAsync() method rather than using the $scope.$apply() externally with the $$phase value check as rightly suggested by #betaorbust:
I recently got a chance to ask Misko (angular author) about $$phase, and he said to never use it; it's an internal implementation of the digest cycle, and it's not future safe.
The good thing about $scope.$evalAsync() is that it gives you what you really want, get the fresh data up with the digest cycle, faster than even the $timeout can:
Up until now, my approach to deferred-$digest-invocation was to replace the $scope.$apply() call with the $timeout() service (which implicitly calls $apply() after a delay). But, yesterday, I discovered the $scope.$evalAsync() method. Both of these accomplish the same thing - they defer expression-evaluation until a later point in time. But, the $scope.$evalAsync() is likely to execute in the same tick of the JavaScript event loop.
Usage is quite simple:
$scope.$evalAsync(
function() {
// Call some method here OR
$scope.excutemyMethod();
// assign some value;
$scope.someVariable = "asd"
}
);
The method inside the $evalAsync() is added to the Async queue so that it can be executed in the following digest cycle by internally triggering $apply which is what we really want.
Also it includes the $timeout call to handle rarest of rare scenarios watching for the async queue length and waiting the tasks in the Async Queue to be executed to put your function in the execution cycle as soon as possible. Refer Ben Nadel blog, which explains this fact.

AngularJS - binding/watching a function which returns a promise

I posted an issue on the AngularJS github but it doesn't seem to be getting a whole lot of attention and I wasn't able to fix it myself since it's a pretty low-level issue, so I think it's time to look for a workaround.
Angular allows you to put a promise (or anything with a .then(...) function) into your scope, and once it is resolved, all $watches and anything bound to that promise will use the resolved value. The issue arises when you use a function to return a promise, as the same doesn't apply - it's handled like a plain object.
For example:
var helloDef = $.Deferred();
$scope.hello = helloDef.promise();
$scope.getHello = function() {
return $scope.hello;
};
$timeout(function() {
helloDef.resolve('Hello!');
}, 1000);
Fiddle
Here using ng-bind="hello" works fine and outputs Hello!, but ng-bind="getHello()" outputs [object Object] as the internal $watch returns the promise object. Works the same way with $q instead of $.Deferred.
In my actual code I'm creating the promise the first time the function is called, so I can't just pre-make the promise and refer to that in scope.
I also need it for more than just ng-bind, so making my own binding directive which handles this case properly isn't viable.
Anyone have any ideas? I'm currently returning the promise if the data hasn't resolved and the actual result if it has, but that's a pain to work with. Anything bound to the data briefly causes weird side effects as the data is being loaded, like ngRepeat using the promise object instead of the resolved value to create elements.
Thanks.
UPDATE: Pull request: https://github.com/angular/angular.js/pull/3605
UPDATE 2: For future reference, automatic promise unwrapping has been deprecated in the 1.2 brach.
For some things, I use $resource. If I need to wait for it, $then works well:
var r = $resource...get...
var p = r.$then...
Otherwise, I build my own resource-like object that is not a promise, but has a promise that I can wait for.
Pull request merged, it should be fixed in 1.2.0 so I'll mark this as the answer.

Resources