There is this article I saw long time ago:
https://coderwall.com/p/ngisma
It describes a method that triggers $apply if we are not in an apply or digest phase.
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
Angular has the $scope.$evalAsync
method (taken from 1.2.14):
$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});
}
Which calls digest if we are not in a phase and adds the current invocation to the asyncQueue.
There is also the $apply, $digest and $timeout methods.
It is confusing.
What is the difference between all ways mentioned to trigger a digest cycle (a dirty check and data binding)?
What is the use case for each method?
Is safeApply() still safe? :)
What alternative we have instead of that safeApply() (in case we call $apply in the middle of a digest cycle)?
Chemical and mechanical digestion are the two methods your body uses to break down foods. Mechanical digestion involves physical movement to make foods smaller. Chemical digestion uses enzymes to break down food.
The digestive system carries out three primary processes: mixing food, moving food through the digestive tract (peristalsis) and using chemicals to break down food into smaller molecules.
Diet composition, exercise, functional disorders like irritable bowel disease (IBS), thyroid dysfunction and metabolic disorders—such as diabetes—to name a few. Food itself also plays a major factor.
As a high level introduction I would say that it is rarely required to actually initiate your own digest cycle, since angular handles most cases.
That being said let's dive into the question.
As a very high level, the $digest loop looks like this:
Do:
- - - If asyncQueue.length, flush asyncQueue.
- - - Trigger all $watch handlers.
- - - Check for "too many" $digest iterations.
While: ( Dirty data || asyncQueue.length )
So basically $evalAsync
is adding the function to the asyncQueue
and defering a digest if it needs to. However if it's already in a digest cycle it will flush the asyncQueue
and it will just call the function.
You may notice that this is very similar to the safeApply
. One difference is that instead of adding the function to the asyncQueue
it just calls it, which can happen in the middle of a cycle for instance. The other difference is that it exposes and relies on a $$
variable, which are intended to be internal.
The most important difference however between $evalAsync
and $apply
from $digest
(I will get to $timeout
below) is that $evalAsync
and $apply
starts the digest at the $rootScope
but you call the $digest
on any scope. You will need to evaluate your individual case if you think you need this flexibility.
Using $timeout
is very similar to $evalAsync
again except that it will always defer it out of the current digest cycle, if there is one.
$timeout(function() {
console.log( "$timeout 1" );
});
$scope.$evalAsync(function( $scope ) {
console.log( "$evalAsync" );
});
If you already in a digest cycle will give you
$evalAsync
$timeout 1
Even though they are called in the opposite order, since the timeout one is delayed to the next digest cycle, which it instantiates.
EDIT For the questions in the comment.
The biggest difference between $apply
and $evalAsync
as far as I can tell is that $apply
is actually triggering the $digest
cycle. To you all this means is that you need to be sure you call $apply
when you aren't in a digest cycle. Just for transparency, the following code is also valid:
$scope.$apply(function() {
$scope.$evalAsync(function() {
});
});
Which will call the apply function, add the no-op function to the $asyncQueue
and begin the digest cycle. The docs say it is recommended to use $apply
whenever the model changes, which could happen in your $evalAsync
The difference between fn(); $scope.apply()
and $scope.apply(fn)
is just that the $scope.apply(fn)
does a try catch for the function and explicitly uses $exceptionHandler
. Additionally, you could actually attempt to cause digest cycles in fn
, which will change how the apply is handled.
I'd also like to point out at this time that it is even more complicated in 1.3 assuming it stays this way. There will be a function called $applyAsync
used to defer apply calls (reference).
This post compiled some information from This blog and this so post plus some of my experience.
Hope this helped!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With