This post was originally published on Jan 22, 2016 but has been updated to reflect the latest information.
At NG-Conf 2014, Brian gave an excellent talk on zones and how they can change the way we deal with asynchronous code. If you haven’t watched this talk yet, give it a shot, it’s just ~15 minutes long. APIs might be different nowadays, but the semantics and underlying concepts are the same. In this article we’d like to dive a bit deeper into how zones work.
The problem to be solved
Let’s recap really quick what zones are. As Brian stated in his talk, they are basically an execution context for asynchronous operations. They turn out to be really useful for things like error handling and profiling. But what exactly does that mean?
Nothing special going on here. We have three functions
baz that are executed in sequence. Let’s say we want to measure the execution time of this code. We could easily extend the snippet with some profiling bits like this:
However, often we have asynchronous work todo. This can be an AJAX request to fetch some data from a remote server, or a maybe we just want to schedule some work for the next frame. Whatever this asynchronous work is, it happens, as the name claims, asynchronously. Which basically means, those operations won’t be considered by our profiler. Take a look at this snippet:
We extended the code sequence with another operation, but this time it’s asynchronous. What effect does that have on our profiling? Well, we’ll see that there’s not such a big difference.
There is in fact one more operation, so it takes slightly longer to execute this code, however, the actual execution time of when the
setTimeout() call returns is not part of the overall profiling. This is because asynchronous operations are added to the browser’s event queue, which eventually gets cleaned up by the event loop once there’s time for that.
If this is entirely new to you, you might want to watch this great talk on how the browser event loops works.
So how do we solve this issue? What we need are basically hooks that allow us to execute some profiling code whenever such an asynchronous task happens. Sure, we could probably create and start an individual timer for each asynchronous operation manually, but that would get quite messy as asynchronous operations are added to the code sequence.
This is exactly where zones come into play. Zones can perform an operation - such as starting or stopping a timer, or saving a stack trace - each time that code enters or exits a zone. They can override methods within our code, or even associate data with individual zones.
Creating, forking and extending Zones
Once we’ve embedded zone.js into our website, we have access to the global
zone comes with a method
run() that takes a function which should be executed in that zone. In other words, if we’d like to run our code in a zone, we can already do it likes this:
Okay cool. But what’s the point of this? Well… currently there’s in fact no difference in the outcome, except that we had to write slightly more code. However, at this point, our code runs in a zone (another execution context) and as we learned earlier, Zones can perform an operation each time our code enters or exits a zone.
In order to set up these hooks, we need to fork the current zone. Forking a zone returns a new zone, which basically inherits from the “parent” zone. However, forking a zone also allows us to extend the returning zone’s behaviour. We can fork a zone by calling
.fork() on the
zone object. Here’s what that could look like:
This really just gives us a new zone with the same power of the original zone (which we haven’t discussed just yet). Let’s try out these hooks we’ve mentioned and extend our new zone. Hooks are defined using a
ZoneSpecification that we can pass to
fork(). We can take advantage of the following hooks:
- onZoneCreated - Runs when zone is forked
- beforeTask - Runs before a function called with
- afterTask - Runs after a function in the zone runs
- onError - Runs when a function passed to
Here’s our code sample with an extended zone that logs before and after each task is executed:
Oh wait! What’s that? Both hooks are executed twice? Why is that? Sure, we’ve learned that
zone.run is obviously considered a “task” which is why the first two messages are logged. But it seems like the
setTimeout() call is treated as a task too. How is that possible?
It turns out that there are a few other hooks. In fact, those aren’t just hooks, but monkey-patched methods on the global scope. As soon as we embed
zone.js in our site, pretty much all methods that cause asynchronous operations are monkey-patched to run in a new zone.
For example, when we call
setTimeout() we actually call
Zone.setTimeout(), which in turn creates a new zone using
zone.fork() in which the given handler is executed. And that’s why our hooks are executed as well, because the forked zone in which the handler will be executed, simply inherits from the parent zone.
There are some other methods that
zone.js overrides by default and provides us as hooks:
We might wonder why methods like
prompt() are patched as well. As mentioned earlier, those patched methods are hooks at the same time. We can change and extend them by forking a zone exactly the same way we did with
afterTask. This turns out to be super powerful, because we can intercept calls to
prompt() and change their behaviour when we write tests.
zone.js comes with a tiny DSL that allows you to augment zone hooks. The project’s readme is probably the best place to take a look at, if you’re interested in this particular thing.
Creating a Profiling Zone
Our original problem was that we couldn’t capture the execution time of asynchronous tasks inside our code. Now with Zones and the provided APIs we’ve learned about, we have actually everything we need to create a zone that profiles the CPU time of our asynchronous tasks. Luckily, such an implementation of a profiling zone is already available as an example in the
zone.js repository and you can find it here.
Here’s what it looks like:
Pretty much the same code as the one we started off with at the beginning of this article, just wrapped in a zone specification. The example also adds a
.reset() method to the zone, which can be invoked on the zone object like this:
+ syntax is a shorthand DSL that allows us to extend the parent zone’s hook. Neat ha?
Watch out for more articles as we’re going to discuss very soon what role Zones play in the Angular 2 framework. Find more useful and related links below.
Angular 2 Animations - Foundation Concepts
Animation in Angular 2 is now easy and more intuitive... Learn foundational animation concepts and start animating your Angular 2...
Angular 2 is out - Get started here
Yes. The day has come. Angular 2 is finally released and here's how to get started.
Bypassing Providers in Angular 2
Dependencies are provided from the nearest ancestor provider in the injector tree. This article shows how to bypass it.
Custom Form Controls in Angular 2
Angular makes it very easy to create custom form controls. Read on to learn how to do it!
Protecting Routes using Guards in Angular 2
Angular's router enables protecting routes using guards and in this article we're going to discuss how to implement them.
Reactive Forms in Angular 2
Angular allows us to build forms in a model-driven fashion. In this article we're going to discuss what that looks...