The differences between AngularJS $apply, $digest, and $evalAsync
Under the hood of AngularJS, there are a lot of evaluation processes that make Angular the awesome framework it is. AngularJS makes the frontend development process much easier and more effective. With the ability to create HTML tags with a special behavior (directives), and to create independent modules, AngularJS is indeed one of the best SPA’s out there.
AngularJS runs in cycles ($digset). That way, AngularJS can evaluate the changes between the model and the view.
In every $digest cycle, the watchers are executed. This is the phase where Angular evaluates the expressions that are attached to the view, and re-renders them back to the user.
This phase is one of the “heavy” parts in terms of performance in AngularJS.
There are a lot of times when operations in the client should and need to be done outside of the “Angular world”, which means Angular is not aware of these changes and does not reflect the changes to the user. There are several methods to resolve this issue, but before rushing and immediately executing the $apply(), or $timeout() methods, you should think about how many watchers and scopes we need to be involved in order to resolve the issue.
$apply() is the function which executes the entire watchers in the application within every scope that we have. This means that every time we call $apply() we execute another full life cycle of our application.
The life cycle involves 3 main tasks.
Taken from AngularJS org
Scope’s $apply() method transitions through the following stages:
1. The expression is executed using the $eval() method.
2. Any exceptions from the execution of the expression are forwarded to the $exceptionHandler service.
3. The watch listeners are fired immediately after the expression was executed using the $digest() method.
There are 2 main problems which can occur by using $apply():
1. $apply() is a heavy process and can lead to performance issues when having a lot of binding.
2. AngularJS has only one cycle which means you can’t run $apply() while another cycle is already in progress. Error: $digest already in progress
So basically, think twice before executing $apply().
$timeout() was the easiest, fastest, and overall best solution to resolve any problems when going outside the “Angular world” prior to AngularJS 1.2.X. The beauty in $timeout() is that it never breaks and will always be completed without Angular “yelling at us” with some nice exception.
The $timeout() tells Angular that after the current cycle, there is a timeout waiting and this way it guarantees that there won’t be collisions between cycles.
To summarize both functions until now, $timeout is the safe way to execute the $apply() function.
$evalAsync() is a new function which was first introduced in AngularJS 1.2.X, and for me it’s the “smarter” brother of $timeout().
Before $evalAsync() was introduced, Officially answered by the Angular team, when you have issues with cycles and want to reflect changes from outside the “Angular world”, use $timeout().
After Angular has evolved and more users have experienced this known issue, the Angular team has created the $evalAsync(). This function will evaluate the expression during the current cycle or the next.
Taken from AngularJS org
$evalAsync() – Executes the expression on the current scope at a later point in time.
The $evalAsync makes no guarantees as to when the expression will be executed, only that:
** it will execute after the function that has scheduled the evaluation (preferably before the DOM rendering).
** at least one $digest cycle will be performed after expression execution.
But what if we don’t want to make a full Angular cycle???
For this reason, there is the $digest() function.
$digest() is the cycle that runs in every scope in order to evaluate its watchers, which we already talked about earlier.
Unlike $apply() which executes $digest() from the $rootScope and to its children, $$digest() starts the $digest loop within the scope he was executed in and downwards to its children. This huge difference can lead to reducing the amount of watchers your application has to implement by the hundreds.
Something important you should be aware of, is that the parent scope is not being updated with the new information, hence the changes won’t be reflected if the expression is also used by the parent scope.
When $scope.$apply() is called, the entire application starts the $digest loop. This cycle re-evaluates all of the watchers of every active scope. In simple terms, it traverses all scopes and bindings of your application to see if things have changed.
Instead of using $scope.$apply(), there are times when we can spare a lot of work from Angular and use the $scope.$digest(), which runs the exact $digest loop, but only from within the scope and bellow (its children’s scopes). The only thing you need to have in mind is that if you’re dependent on two-way binding between Objects from the parent scope, then they won’t be reflected.