Книга: Angular 2 Cookbook
Назад: Understanding and properly utilizing enableProdMode with pure and impure pipes
Дальше: Listening for NgZone events

Working with zones outside Angular

Working with zones entirely inside of the Angular framework conceals what they are really doing behind the scenes. It would be a disservice to you, the reader, to just gloss over the underlying mechanism. In this recipe, you'll take the vanilla zone.js implementation outside of Angular and modify it a bit in order to see how Angular can make use of it.

There will be no Angular used in this recipe, only zone.js inside a simple HTML file. Furthermore, this recipe will be written in plain ES5 JavaScript for simplicity.

Note

The code, links, and a live example related to this recipe are available at .

Getting ready

Begin with the following simple HTML file:

[index.html]      <button id="button">Click me</button>   <button id="add">Add listener</button>   <button id="remove">Remove listener</button>      <script>   var button = document.getElementById('button');   var add = document.getElementById('add');   var remove = document.getElementById('remove');      var clickCallback = function() {     console.log('click!');   };      setInterval(function() {     console.log('set interval!');   }, 1000);        add.addEventListener('click', function() {     button.addEventListener('click', clickCallback);   });        remove.addEventListener('click', function() {     button.removeEventListener('click', clickCallback);   });   </script>   

Each of these callbacks has log statements inside them so you can see when they are invoked:

  • setInterval calls its associated listener every second
  • Clicking on Click me calls its listener if it is attached
  • Clicking on Add listener attaches a click listener to the button
  • Clicking on Remove listener removes the click listener

Note

There's nothing special going on here, because all of these are default browser APIs. The magic of zones, as you will see, is that the zone behavior can be introduced around this without modifying any code.

How to do it...

First, add the zone.js script to the top of the file:

[index.html]       <script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.6.26/zone.js">   </script>      <button id="button">Click me</button>   <button id="add">Add listener</button>   <button id="remove">Remove listener</button>   ...   

Note

There's no special setup needed for zone.js, but it needs to be added before you set listeners or do anything that could have asynchronous implications. Angular needs this dependency to be added before it is initialized.

Adding this script introduces Zone to the global namespace. zone.js has already created a global zone for you. This can be accessed with the following:

Zone.current   

Forking a zone

The global zone isn't doing anything interesting yet. To customize a zone, you'll need to create your own by forking the one we have and running relevant code inside it. Do this as follows:

[index.html]      <script    src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.6.26/zone.js">  </script>      <button id="button">Click me</button>   <button id="add">Add listener</button>   <button id="remove">Remove listener</button>      <script>   var button = document.getElementById('button');   var add = document.getElementById('add');   var remove = document.getElementById('remove');      Zone.current.fork({}).run(function() {      var clickCallback = function() {       console.log('click!');     };        setInterval(function() {       console.log('set interval!');     }, 1000);          add.addEventListener('click', function() {      button.addEventListener('click', clickCallback);     });          remove.addEventListener('click', function() {      button.removeEventListener('click', clickCallback);     });   });   </script>   

Behaviorally, this doesn't change anything from the perspective of the console. fork() takes an empty ZoneSpec object literal, which you will modify next.

Overriding zone events with ZoneSpec

When a piece of code is run in a zone, you are able to attach behaviors at important points in the asynchronous behavior flow. Here, you'll override four zone events:

  • scheduleTask
  • invokeTask
  • hasTask
  • cancelTask

You'll begin with scheduleTask. Define an override method inside ZoneSpec. Overrides use the event names prefixed with on:

[index.html]      <script    src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.6.26/zone.js">  </script>      <button id="button">Click me</button>   <button id="add">Add listener</button>   <button id="remove">Remove listener</button>      <script>   var button = document.getElementById('button');   var add = document.getElementById('add');   var remove = document.getElementById('remove');      Zone.current.fork({     onScheduleTask: function(zoneDelegate, zone, targetZone, task) {       console.log('schedule');       zoneDelegate         .scheduleTask(targetZone, task);     }   }).run(function() {     var clickCallback = function() {       console.log('click!');     };        setInterval(function() {       console.log('set interval!');     }, 1000);          add.addEventListener('click', function() {      button.addEventListener('click', clickCallback);     });          remove.addEventListener('click', function() {      button.removeEventListener('click', clickCallback);     });   });   </script>   

With this addition, you should see that the zone recognizes that three tasks are being scheduled. This should make sense, as you are declaring three instances that can generate asynchronous actions: setInterval, addEventListener, and removeEventListener. If you click on Add listener, you'll see it schedule the fourth task as well.

Note

zoneDelegate.scheduleTask() is required because you are actually overwriting what the zone is using for that handler. If you don't perform this action, the scheduler handler will exit before actually scheduling the task.

The opposite of scheduling a task is canceling it, so override that event handler next:

[index.html]      Zone.current.fork({     onScheduleTask: function(zoneDelegate, zone, targetZone, task) {       console.log('schedule');       zoneDelegate         .scheduleTask(targetZone, task);     },     onCancelTask: function(zoneDelegate, zone, targetZone, task) {       console.log('cancel');       zoneDelegate         .cancelTask(targetZone, task);     }   }).run(function() {     ...   });   

A cancel event occurs when a scheduled task is destroyed, in this example, when removeEventListener() is invoked. If you click on Add listener and then Remove listener, you'll see a cancel event occur.

The scheduling of tasks is visible during the startup, but each time a button is clicked or a setInterval handler is executed, you don't see anything being logged. This is because scheduling a task, which occurs during registration, is distinct from invoking a task, which is when the asynchronous event actually occurs.

To demonstrate this, add an invokeTask override:

[index.html]      Zone.current.fork({     onScheduleTask: function(zoneDelegate, zone, targetZone, task) {       console.log('schedule');       zoneDelegate         .scheduleTask(targetZone, task);     },     onCancelTask: function(zoneDelegate, zone, targetZone, task) {       console.log('cancel');       zoneDelegate         .cancelTask(targetZone, task);     },     onInvokeTask: function(zoneDelegate, zone, targetZone, task,                             applyThis, applyArgs) {        console.log('invoke');       zoneDelegate         .invokeTask(targetZone, task, applyThis, applyArgs);     }   }).run(function() {     ...   });   

With this addition, you should now be able to see a console log each time a task is invoked—for a button click or a setInterval callback.

So far, you've been able to see when the zone has tasks scheduled and invoked, but now, do the reverse of this to detect when all the tasks have been completed. This can be accomplished with hasTask, which can also be overridden:

[index.html]      Zone.current.fork({     onScheduleTask: function(zoneDelegate, zone, targetZone, task) {       console.log('schedule');       zoneDelegate         .scheduleTask(targetZone, task);     },     onCancelTask: function(zoneDelegate, zone, targetZone, task) {       console.log('cancel');       zoneDelegate         .cancelTask(targetZone, task);     },     onInvokeTask: function(zoneDelegate, zone, targetZone, task,                             applyThis, applyArgs) {        console.log('invoke');       zoneDelegate         .invokeTask(targetZone, task, applyThis, applyArgs);     },     onHasTask: function(zoneDelegate, zone, targetZone, isEmpty) {       console.log('has');       zoneDelegate.hasTask(targetZone, isEmpty);     }   }).run(function() {     ...   });   

The isEmpty parameter of onHasTask has three properties: eventTask, macroTask, and microTask. These three properties map to Booleans describing whether the associated queues have any tasks inside them.

With these four callbacks, you have successfully intercepted four important points in the component life cycle:

  • When a task "generator" is scheduled, which may generate tasks from browser or timer events
  • When a task "generator" is canceled
  • When a task is actually invoked
  • How to determine whether any tasks are scheduled and of what type

How it works...

The concept that forms the core of zones is the interception of asynchronous tasks. More directly, you want the ability to know when asynchronous tasks are being created, how many there are, and when they're done.

zone.js accomplishes this by shimming all the relevant browser methods that are responsible for setting up asynchronous tasks. In fact, all the methods used in this method—setInterval, addEventListener, and removeEventListener—are all shimmed so that the zone they are run inside is aware of any asynchronous tasks they might add to the queue.

There's more...

To begin to relate this to Angular, you'll need to take a step back to examine the zone ecosystem.

Understanding zone.run()

You'll notice in this example that invoke is printed for each asynchronous action, even for those that were registered inside another asynchronous action. This is the power of zones. Anything done inside the zone.run() block will cascade within the same zone. This way, the zone can keep track of an unbroken segment of asynchronous behavior without an ocean of boilerplate code.

Microtasks and macrotasks

This actually has nothing to do with zone.js at all but rather with how the browser event loop works. All the events generated by you in this example—clicks, timer events, and so on—are macrotasks. That is, the browser respects their handlers as a synchronous, blocking segment of code. The code that executes around these tasks—the zone.js callbacks, for example—are microtasks. They are distinct from macrotasks but are still synchronously executed as part of the entire "turn" for that macrotask.

Note

A macrotask may generate more microtasks for itself within its own turn.

Once the microtask and macrotask queues are empty, the zone can be considered to be stable, since there is no asynchronous behavior to be anticipated. For Angular, this sounds like a great time to update the UI.

In fact, this is exactly what Angular is doing behind the scenes. Angular views the browser through the task-centric goggles of zone.js and uses this clever tool to decide when to go about rendering.

See also

  • Listening for NgZone events gives a basic understanding of how Angular is using zones
  • Execution outside the Angular zone shows you how to perform long-running operations without incurring a zone overhead
  • Configuring components to use explicit change detection with OnPush describes how to manually control Angular's change detection process
Назад: Understanding and properly utilizing enableProdMode with pure and impure pipes
Дальше: Listening for NgZone events

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already