What you don't know about zone.js

@JiaLiPassion

Who am I

  • Name: Jia Li
  • Company: ThisDot
  • Zone.js: Code Owner
  • Angular: Collaborator

Agenda

  • What's Zone.js
  • Zone.js in Angular
  • Some features you may not know

What is Zone.js

A Zone is an execution context that persists across async tasks.

Created by Brian Ford inspired by Dart.

What zone.js can do

  • Provide execution context

  • Provide async task lifecycle hook

  • Provide error handler for async operations

Execution Context

Zone.current.fork({name: 'zone', properties: {key: 'value'}})
   .run(function() {
      setTimeout(function () {
        console.log(Zone.current.name, Zone.current.get('key')); 
        // zone value
      }, 100);
      setTimeout(function() {
        console.log(Zone.current.name, Zone.current.get('key')); 
        // zone value
      }, 200);
      Promise.resolve(1).then(() => {
        console.log(Zone.current.name, Zone.current.get('key')); 
        // zone value
      });
});

LifeCycle Interceptable Hooks

  • onFork
  • onIntercept
  • onInvoke
  • onHandleError
  • onScheduleTask
  • onInvokeTask
  • onCancelTask
  • onHasTask

setTimeout

setTimeout(function() {
  console.log('callback invoked');
}, 1000);

setTimeout in Zone

var zone = Zone.current.fork({
  name: 'hook',
  onScheduleTask: ...,
  onInvokeTask: ....,
  onHasTask: ...
});
zone.run(() => {
  setTimeout(() => {
    console.log('timer callback invoked');
  }, 100);
});

Implementation: Mokey-Patch

const nativeTimeout = window.setTimeout;
window.setTimeout = function(callback) {
  const zone = Zone.current;
  const wrappedCallback = function() {
     zone.onInvokeTask('setTimeout'); // invoke hook
     return callback.apply(this, arguments);
  }
  zone.onScheduleTask('setTimeout');  // schedule hook
  nativeTimeout.apply(this, wrappedCallback);
}

Async Error Handling

Zone.current.fork(
  {
    name: 'error',
    onHandleError: (delegate, curr, target, error) => {
       logger.error(error);
       return delegate.handleError(target, error);
    }
}).runGuarded(() => {
    setTimeout(() => {
       throw new Error('timeout error');
    });
    setTimeout(() => {
       throw new Error('another timeout error');
    });
});

When should we use Zone

  • Test
    • Sync(Disallow Async)
    • Async(Auto done, Auto cleanup)
    • FakeAsync()
  • Debug
    • LongStackTrace
    • Task Tracing
  • Performance measure
  • Framework AutoRender
  • User Action Tracing
  • Resource Releasing

Zone.js in Angular

NgZone

Change Detection

AsyncTest

FakeAsyncTest

SyncTest

Error Handling

Debug/Tracing

TaskTrackingZone

LongStackTraceZone

Features of zone.js you may not know

Module

Timer

Promise

EventTarget

...

requestAnimationFrame

Disable specified Module

(window as any).__Zone_disable_requestAnimationFrame = true;

Ionic v4

(window as any).__Zone_disable_customElements = true;

Disable Specified Event

// recommended variable name
__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove'];

// deprecated variable name
// __zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove'];

Native Delegate

// ngZone.runOutsideAngular(() => {});

declare let Zone: any;
(window as any)[Zone.__symbol__('setTimeout')](() => {
  // will not in zone.
}, 100);

Promise

ZoneAwarePromise

  • Pass Promise A+ Test
  • Support Promise.prototype.finally
  • Support Bluebird
    • All Bluebird specified APIs will in zone
  • Will support Promise.any and Promise.allSettled

Error Handling

import 'zone.js/dist/zone-error';

Error Handling

import 'zone.js/dist/zone-error';
class MyError extends Error {
}

try {
  throw new MyError('myError');
} catch (error: Error) {
  if (error instanceof MyError) {
    // do some special error handling
  }
}
(window as any).__Zone_Error_BlacklistedStackFrames_policy
   = 'disable'; 

Electron

  • Browser
  • NodeJs
  • Menu
  • Screenshot
  • Shell
  • Main<->Render communication
import 'zone.js/dist/zone-mix';
import 'zone.js/dist/zone-patch-electron';

More APIs Support

  • MediaQuery

  • Notification

  • RTCPeerConnction

  • ShadyDom

  • Cordova

  • ResizeObserver

  • SocketIO

  • UserMedia

  • Jsonp Helper

FakeAsync: Date.now

will support performance api later

it('should advance Date with tick in fakeAsync', 
  fakeAsync(() => {
    const start = Date.now();
    tick(100);
    const end = Date.now();
    expect(end - start).toBe(100);
}));

FakeAsync: jasmine.clock

beforeEach(() => {
      jasmine.clock().install();
});
afterEach(() => {
      jasmine.clock().uninstall();
});
it('should get date diff correctly', () => {
      // automatically run into fake async zone,
      const start = Date.now();
      jasmine.clock().tick(100);
      const end = Date.now();
      expect(end - start).toBe(100);
});

__zone_symbol__fakeAsyncAutoFakeAsyncWhenClockPatched

FakeAsync: rxjs.scheduler

import 'zone.js/dist/zone-patch-rxjs-fake-async';

it('rxjs scheduler should be advanced by tick in fakeAsync', 
  fakeAsync(() => {
        observable.delay(1000).subscribe(v => {
          result = v;
        });
        expect(result).toBeNull();
        testZoneSpec.tick(1000);
        expect(result).toBe('hello');
}));

Better timeout message

it('should timeout', async(() => {
    setTimeout(() => {}, 10000);
    fixture.whenStable(() => {});
}));

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
--Pendng async tasks are: [type: macroTask, source: setTimeout, args: {handleId:3,isPeriodic:false,delay:10000,args:[]}

Jasmine 3.x/Mocha 5.x

Will support Jest!

  • Better error message
  • Better jest timer(useFakeTimers/runAllTicks/etc...) support

Differential Loading

<script src="zone.js/dist/zone-evergreen.js" 
  type="module"></script>
<script src="zone.js/dist/zone.js" nomodule></script>

Event Bubble Performance

<div (click)="doSomethingElse()">
  <button (click)="doSomething()">click me</button>
</div>
platformBrowserDynamic()
  .bootstrapModule(
  AppModule, 
  {
    ngZoneEventCoalescing: true
  }
)

Solution

Event Listeners

button.addEventListener('click', click1);
button.addEventListener('click', click2);
button.addEventListener('mousedown', mousedown1);

const clickListeners = button.eventListeners('click');
// will get click1 and click2

button.removeAllListeners('click');
// will remove click1 and click2

button.removeAllListeners();
// will remove all listeners for all events

Zone.js is merged into angular mono repo !!!

Thank you!

You don't know about zone.js

By jiali

You don't know about zone.js

  • 2,205