What's new in Zone.js

@JiaLiPassion

Who am I

  • Name: LI JIA
  • Company: Sylabs
  • Zone.js: Collaborator
  • Angular: Contributor

Agenda

  • What's Zone.js
  • Zone.js in Angular
  • What's new in zone.js will help you in angular

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 interceptable hook

  • Provide async error handler

Execution Context

Zone.current.fork({name: 'a'})
   .run(function() {
      setTimeout(function () {
        // Zone.current is a
      }, 100);
      setTimeout(function() {
        // Zone.current is a
      }, 200);
});
function outside() {
   // Execution context of outside
   function inside1() {
       // Execution context of inside1
   }

   function inside2() {
       // Execution context of inside2
   }
}

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);
});

Zone.js Event loop Demo

https://zone-ebb17.firebaseapp.com/

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');
    });
});

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);
}

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

Change Detection

AsyncTest

FakeAsyncTest

SyncTest

Error Handling

Debug/Tracing

Zone.js in Angular

DOM API

setTimeout

Promise

EventTarget

...

Zone.js

Monkey-Patch

Angular

What's new

  • Performance
  • Promise
  • Error handling
  • Electron
  • More APIs support
  • Test
  • Angular Elements (Proposal)

Performance

  • Modularization
  • Event
  • Native Delegate

Modularization

 

Timer

Promise

EventTarget

requestAnimationFrame

...

Notification

MediaQuery

UserMedia

...

requestAnimationFrame

__Zone_disable_requestAnimationFrame=true

Event

__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove'];


@HostListener('window:mousemove.nozone')
mouseMoveListener() {
  // will not in ngZone.
} 

<div (scroll.nozone)="scrollHandler();"></div>

Proposal?

Native Delegate

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

Promise

  • Pass Promise A+ Test
  • Support Promise.prototype.finally
  • Support Bluebird
    • All Bluebird specified APIs will in zone
  • No more 'Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten ' error.
  • No more 'Zone already loaded' error. (proposal)
import 'zone.js/dist/zone';
import 'core-js/promise';

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'; // 'default', 'lazy'

Electron

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

More APIs Support

  • MediaQuery

  • Notification

  • RTCPeerConnction

  • ShadyDom

  • Cordova

  • ResizeObserver

  • SocketIO

  • UserMedia

  • Jsonp Helper

Test

  • Bundle
  • FakeAsync enhancement
  • Async enhancement
  • Jasmine/Mocha
  • Unified Test Lib

Zone.js can be a standalone test library.

Test Bundle

import 'zone.js/dist/long-stack-trace';
import 'zone.js/dist/proxy';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async';
import 'zone.js/dist/promise-testing';
import 'zone.js/dist/zone-testing';

Test Bundle(Proposal)

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

describe('zone-testing', () => {
  it('fakeAsync', fakeAsyncTest(() => {
    tick(100);
    discardPeriodicTasks();
    flushMicrotasks();
  });
 
  it ('async', asyncTest(() => {}));
});

FakeAsync: Date.now

it('should advance Date when 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', () => {  // we don't need fakeAsync here.
      // automatically run into fake async zone, because jasmine.clock() is installed.
      const start = Date.now();
      jasmine.clock().tick(100);
      const end = Date.now();
      expect(end - start).toBe(100);
});

__zone_symbol__fakeAsyncPatchLock

FakeAsync: rxjs.scheduler

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

Unified zone-testing

  • Can auto detect Test Framework
  • Can write mixed style test cases at the same time.
    • Jasmine
    • mocha
    • jest

Unify TDD/BDD I/F

describe('BDD style', () => {});

xdescribe('BDD style', () => {});

suite('mocha style', () => {});

describe('', () => {
  beforeEach(() => {});
  suiteSetup(() => {});
  before(() => {});
  beforeAll(() => {});
});

describe('', () => {
  it('case', () => {});
  xit('case', () => {});
  it.skip('mocha skip', () => {});
  it.only('mocha only', () => {});
  specify('mocha specify', () => {});
});
Jasmine Mocha Jest
beforeAll before beforeAll
afterAll after afterAll
xdescribe describe.skip describe.skip
fdescribe describe.only describe.only
xit it.skip it.skip
fit it.only it.only

Mocha BDD I/F

Specify, suite, suiteSetup, suiteTearDown...

Unify expect

it ('expect', () => {
  expect(a).toEqual('test');  // jasmine
  expect(b).toBeInstanceOf(BType); // jest
  expect(Promise.resolve('lemon')).resolves.toBe('lemon'); // jest 
  const expected = [
      expect.stringMatching(/^Alic/),
      expect.stringMatching(/^[BR]ob/),
  ];
  expect(['Alicia', 'Roberto', 'Evelina']).toEqual(expect.arrayContaining(expected));  // jest
  const onPress = {x: 100, y: 200, z: 300};
  expect(onPress).toEqual(expect.objectContaining({
        x: expect.any(Number),
        y: expect.any(Number),
  }), ); // jest
  // ...
});

Unify spy

it ('expect', async() => {
  const spy = jasmine.createSpy();
  const spy1 = spyOn(obj);
  const spy2 = jest.mockFn();
  const myMockFn = jest.fn(() => 'default')
                  .mockImplementationOnce(() => 'first call')
                  .mockImplementationOnce(() => 'second call'); // jest
   const asyncMock = jest.fn()
                  .mockResolvedValue('default');
   expect(await asyncMock()).toEqual('default'); // jest
});

Unify Fake Timer

describe('', () => {
  beforeEach(() => jasmine.clock().install);
  afterEach(() => jasmine.clock().uninstall);
});

describe('', () => {
  beforeEach(() => {jest.useFakeTimers()});
  afterEach(() => {jest.resetFakeTimers();});
});

Angular Elements (Scoped Zone Proposal)

Web App

JQuery

React

Angular Elements

Zone.js

Zone.js

Zone.js

Update recently for Angular Elements

  • Custom Element V1 Support
  • Support loading Zone.js multiple times
  • Optimized root zone
  • rxjs 6/typescript 3

Update recently for Angular Elements

async function test() {
  return 1; 
}

async function main() {
  console.log('before await', Zone.current.name);
  await test();
  console.log('after await', Zone.current.name);
}

Zone.current.fork({name: 'test'}).run(main);

Thank You!

  • https://github.com/angular/zone.js
  • @JiaLiPassion

devtv

By jiali

devtv

  • 2,274