Go fast with $applyAsync in Angular 1.3
As already mentioned in our articles on one-time bindings and disabling debug info, one of the biggest goals of the 1.3 release was to improve Angular’s overall performance.
This article details yet another nice feature that makes your Angular applications in particular cases potentially faster: It lets you resolve multiple $http
responses, that are received around the same time, in one $digest
cycle with a new API added to the $rootScope
called $applyAsync
.
Let’s talk about what that actually means and why you want to do that.
Why and when we need the $digest
cycle
We know that two-way data binding is one big selling point of Angular. Changes to our model in the imperative world of JavaScript seem to sync magically with the model values in the declarative world of HTML and vice versa, without us setting up any event listener and other things that are required to achieve that functionality.
In fact, two-way binding is just one kind of binding that Angular supports. We also have one-way bindings and even one-time bindings (since version 1.3) as mentioned earlier. In order to make data binding possible, Angular comes with this sort of event loop (the $digest
) to update our application model and DOM, whenever it is needed.
But how does Angular know, when it has to trigger another $digest
cycle? We don’t want to go in too much detail here, since there are ton of resources out there that cover this topic very well, but let’s clarify at least the most important facts. Some people think initially, that Angular has a kind of poll mechanism that checks every few milliseconds if something on the model changed so it can update the view accordingly. This is not true.
There are basically three possible cases when the state of an application can change and these are the only moments where $digest
cycles are needed. The case are:
- User Interaction through events - The user clicks UI controls like buttons and in turn triggers something in our application that changes state.
- XMLHttpRequests - Also known as AJAX. Something in our app requests some data from a server and update model data accordingly.
- Timeouts - Asynchronous operations cause through timers that can possibly change the state of our application
Whenever one of the above things happens, Angular knows it needs to trigger a $digest
. You might still wonder how that works, since you don’t have to inform Angular about these interactions explicitly. This is because Angular intercepts all of these interactions already for you.
That’s why we have all these predefined directives like ng-click
or even directives that override existing tags like <input>
. The $http
service that Angular brings to the table also makes sure that a $digest
is triggered once a request returns.
In addtion, this explains why you need to call $scope.$apply()
, which in turn triggers a $digest
internally, when you have third-party code that changes your application’s state from the outside world.
Okay, so now we have a general picture of what the $digest
is about and when it’s needed and triggered and also how we can trigger it explicitly with $scope.$apply
. But we haven’t talked about when $applyAsync
comes into play.
Batching multiple $http
responses into one $digest
As the name already says, $applyAsync
has something to do with executing a $scope.$apply
through an asynchronous operation. But what does that mean and when makes it actually sense?
We mentioned that one of the cases where a $digest
is triggered, is when an XHR call using $http
service returns from it’s execution. This is nice because we don’t have to worry about updating our model in the DOM once the model is updated. Here’s a small snippet that details that scenario (note that we don’t use $scope
here since we assume that controllerAs syntax is used:
app.controller('Ctrl', function ($http) {
// Make XHR and update model accordingly
$http.get('fetch/some/json/').then(function (response) {
this.myModel = response.data;
}.bind(this));
});
We have a controller that asks for $http
service and uses it to make an XHR to some url and once the call resolves, we update myModel
on our controller with the new data that we got from the server. There’s nothing we need to do to update myModel
in our DOM, since this call, once it resolves, triggers a $digest
that takes care of the rest.
Now imagine we build an application where it’s required to make three XHRs at bootstrap time. That means, three independent requests that all resolve independently after different periods of time, which in turn causes three $digest
cycles that get triggered once each of the calls return. This can slow down our application. Wouldn’t it be nice if we could collect the promises that return from the XHR calls that are made around the same time and resolve them at the next $digest
cycle that happens? Yes! And this is exactly where $applyAsync
comes into play.
Since Angular 1.3, $rootScope
comes with a new method $applyAsync that lets us basically collect expressions. These expressions get immediately evaluated but resolved with the next tick ($digest
). In order to make this work nice with requests that happen through $http
calls, $httpProvider
comes with a corresponding API that tells Angular that we actually want to use that feature.
All we need to do is to call the provider’s useApplyAsync method and Angular takes care of deferring the resolution of your XHR calls to the next tick. Here’s what it looks like:
app.config(function ($httpProvider) {
$httpProvider.useApplyAsync(true);
});
That’s it! If the application now receives multiple $http
responses at around the same time, this is what happens (a bit simplified though):
- The call’s promise is pushed into a queue
- An asynchronous
$apply
is scheduled in case there’s no one scheduled yet, by telling the browser to executesetTimeout()
- Once timed out, the queue is flushed and the actual
$apply
is triggered
The setTimeout()
is called with a 0
delay which causes an actual delay of around 10 milliseconds depending on the browser. That means, if our three asynchronous calls return at around the same time (somewhere inside that particular timeout delay), they get resolve with a single $digest
cycle instead of three which speeds up our application.