With the introduction of Angular 2 comes the concept of zones. Before you begin this recipe, I strongly recommended you to begin by working through the Working with zones outside Angular recipe.
zone.js
is a library that Angular 2 directly depends upon. It allows Angular to be built upon a zone that allows the framework to intimately manage its execution context.
More plainly, this means that Angular can tell when asynchronous things are happening that it might care about. If this sounds a bit like how $scope.apply()
was relevant in Angular 1.x, you are thinking in the right way.
Angular 2's integration with zones takes the form of the NgZone
service, which acts as a sort of wrapper for the actual Angular zones. This service exposes a useful API that you can tap into.
The code, links, and a live example related to this recipe are available at .
All that is needed for this recipe is a component into which the NgZone
service can be injected:
[src/app/app.component.ts] import {Component} from '@angular/core'; @Component({ selector: 'app-root', template: `` }) export class AppComponent { constructor() { } }
Begin by injecting the NgZone
service into your component, which is made available inside the core Angular module:
[src/app/app.component.ts] import {Component, NgZone} from '@angular/core'; @Component({ selector: 'app-root', template: `` }) export class AppComponent { constructor(private zone:NgZone) { } }
The NgZone
service exposes a number of EventEmitters
that you can attach to. Since zones are capable of tracking asynchronous activity within it, the NgZone
service exposes two EventEmitters
that understand when an asynchronous activity becomes enqueued and dequeued as microtasks.
The onUnstable EventEmitter
lets you know when one or more microtasks are enqueued; onStable
is fired when the the microtask queue is empty and Angular does not plan to enqueue any more.
Add handlers to each:
[src/app/app.component.ts] import {Component, NgZone} from '@angular/core'; @Component({ selector: 'app-root', template: `` }) export class AppComponent { constructor(private zone:NgZone) { zone.onStable.subscribe(() => console.log('stable')); zone.onUnstable.subscribe(() => console.log('unstable')); } }
Terrific! However, the log output of this is quite boring. At application startup, you'll see that the application is reported as stable, but nothing further.
If you understand how Angular uses zones, the lack of logging shouldn't surprise you. There's nothing to generate asynchronous tasks in this zone. Go ahead and add some by creating a button with a handler that sets a timeout log statement:
[src/app/app.component.ts] import {Component, NgZone} from '@angular/core'; @Component({ selector: 'app-root', template: `<button (click)="foo()">foo</button>` }) export class AppComponent { constructor(private zone:NgZone) { zone.onStable.subscribe(() => console.log('stable')); zone.onUnstable.subscribe(() => console.log('unstable')); } foo() { setTimeout(() => console.log('timeout handler'), 1000); } }
Now with each click, you should see an unstable-stable pair, followed by an unstable-timeout handler-stable set one second later. This means you've successfully tied into Angular's zone emitters.
In order to obviate the necessity of a $scope.apply()
construct, Angular needs the ability to intelligently decide when it should check to see whether the state of the application has changed.
In an asynchronous setting, such as a browser environment, this seems like a messy task at first. Something like timed events and input events are, by their very nature, difficult to keep track of. For example, refer to the following code:
element.addEventListener('click', _ => { // do some stuff setInterval(_ => { // do some stuff }, 1000); });
This code is capable of changing the model in two different places, both of which are asynchronous. Code such as this is written all the time and in so many different places; it's difficult to imagine a way of keeping track of such code without sprinkling something like $scope.$apply()
all over the place.
The big idea of zones is to give you the ability to grasp how and when the browser is performing asynchronous actions that you care about.
NgZone
is wrapping the underlying zone API for you instead of exposing EventEmitters
to the various parts of the life cycle, but this shouldn't confuse you one bit. For this example, the log output is demonstrating the following:
setTimeout
handler to the task queue. Since this is shimmed by the zone, Angular sees this occur and emits an unstable event.setTimeout
handler is executed.setTimeout
handler is completed, and the application once again has no pending tasks. Angular emits a stable event.