From afb4bd9ef60dcc0ac4c7acde16fca3d48d2129ee Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 6 Oct 2016 15:23:37 -0700 Subject: [PATCH] refactor(NgZone): merge NgZoneImpl and NgZone (#12104) --- modules/@angular/core/src/zone/ng_zone.ts | 262 ++++++++++-------- .../@angular/core/src/zone/ng_zone_impl.ts | 98 ------- 2 files changed, 154 insertions(+), 206 deletions(-) delete mode 100644 modules/@angular/core/src/zone/ng_zone_impl.ts diff --git a/modules/@angular/core/src/zone/ng_zone.ts b/modules/@angular/core/src/zone/ng_zone.ts index 6f90f722fe..5ccc434534 100644 --- a/modules/@angular/core/src/zone/ng_zone.ts +++ b/modules/@angular/core/src/zone/ng_zone.ts @@ -8,25 +8,22 @@ import {EventEmitter} from '../facade/async'; -import {NgZoneImpl} from './ng_zone_impl'; - - /** * An injectable service for executing work inside or outside of the Angular zone. * * The most common use of this service is to optimize performance when starting a work consisting of * one or more asynchronous tasks that don't require UI updates or error handling to be handled by - * Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks - * can reenter the Angular zone via {@link #run}. + * Angular. Such tasks can be kicked off via {@link runOutsideAngular} and if needed, these tasks + * can reenter the Angular zone via {@link run}. * * * - * ### Example ([live demo](http://plnkr.co/edit/lY9m8HLy7z06vDoUaSN2?p=preview)) + * ### Example * ``` - * import {Component, View, NgZone} from '@angular/core'; + * import {Component, NgZone} from '@angular/core'; * import {NgIf} from '@angular/common'; * * @Component({ @@ -67,7 +64,6 @@ import {NgZoneImpl} from './ng_zone_impl'; * }})); * } * - * * _increaseProgress(doneCallback: () => void) { * this.progress += 1; * console.log(`Current progress: ${this.progress}%`); @@ -83,82 +79,85 @@ import {NgZoneImpl} from './ng_zone_impl'; * @experimental */ export class NgZone { - static isInAngularZone(): boolean { return NgZoneImpl.isInAngularZone(); } - static assertInAngularZone(): void { - if (!NgZoneImpl.isInAngularZone()) { - throw new Error('Expected to be in Angular Zone, but it is not!'); - } - } - static assertNotInAngularZone(): void { - if (NgZoneImpl.isInAngularZone()) { - throw new Error('Expected to not be in Angular Zone, but it is!'); - } - } - - private _zoneImpl: NgZoneImpl; + private outer: Zone; + private inner: Zone; private _hasPendingMicrotasks: boolean = false; private _hasPendingMacrotasks: boolean = false; - /** @internal */ private _isStable = true; - /** @internal */ - private _nesting = 0; - /** @internal */ + private _nesting: number = 0; private _onUnstable: EventEmitter = new EventEmitter(false); - /** @internal */ private _onMicrotaskEmpty: EventEmitter = new EventEmitter(false); - /** @internal */ private _onStable: EventEmitter = new EventEmitter(false); - /** @internal */ private _onErrorEvents: EventEmitter = new EventEmitter(false); constructor({enableLongStackTrace = false}) { - this._zoneImpl = new NgZoneImpl({ - trace: enableLongStackTrace, - onEnter: () => { - // console.log('ZONE.enter', this._nesting, this._isStable); - this._nesting++; - if (this._isStable) { - this._isStable = false; - this._onUnstable.emit(null); - } - }, - onLeave: () => { - this._nesting--; - // console.log('ZONE.leave', this._nesting, this._isStable); - this._checkStable(); - }, - setMicrotask: (hasMicrotasks: boolean) => { - this._hasPendingMicrotasks = hasMicrotasks; - this._checkStable(); - }, - setMacrotask: (hasMacrotasks: boolean) => { this._hasPendingMacrotasks = hasMacrotasks; }, - onError: (error: any) => this._onErrorEvents.emit(error) - }); + if (typeof Zone == 'undefined') { + throw new Error('Angular requires Zone.js prolyfill.'); + } + + Zone.assertZonePatched(); + + this.outer = this.inner = Zone.current; + + if ((Zone as any)['wtfZoneSpec']) { + this.inner = this.inner.fork((Zone as any)['wtfZoneSpec']); + } + + if (enableLongStackTrace && (Zone as any)['longStackTraceZoneSpec']) { + this.inner = this.inner.fork((Zone as any)['longStackTraceZoneSpec']); + } + + this.forkInnerZoneWithAngularBehavior(); } - private _checkStable() { - if (this._nesting == 0) { - if (!this._hasPendingMicrotasks && !this._isStable) { - try { - // console.log('ZONE.microtaskEmpty'); - this._nesting++; - this._onMicrotaskEmpty.emit(null); - } finally { - this._nesting--; - if (!this._hasPendingMicrotasks) { - try { - // console.log('ZONE.stable', this._nesting, this._isStable); - this.runOutsideAngular(() => this._onStable.emit(null)); - } finally { - this._isStable = true; - } - } - } - } + static isInAngularZone(): boolean { return Zone.current.get('isAngularZone') === true; } + + static assertInAngularZone(): void { + if (!NgZone.isInAngularZone()) { + throw new Error('Expected to be in Angular Zone, but it is not!'); } - }; + } + static assertNotInAngularZone(): void { + if (NgZone.isInAngularZone()) { + throw new Error('Expected to not be in Angular Zone, but it is!'); + } + } + + /** + * Executes the `fn` function synchronously within the Angular zone and returns value returned by + * the function. + * + * Running functions via `run` allows you to reenter Angular zone from a task that was executed + * outside of the Angular zone (typically started via {@link runOutsideAngular}). + * + * Any future tasks or microtasks scheduled from within this function will continue executing from + * within the Angular zone. + * + * If a synchronous error happens it will be rethrown and not reported via `onError`. + */ + run(fn: () => any): any { return this.inner.run(fn); } + + /** + * Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not + * rethrown. + */ + runGuarded(fn: () => any): any { return this.inner.runGuarded(fn); } + + /** + * Executes the `fn` function synchronously in Angular's parent zone and returns value returned by + * the function. + * + * Running functions via `runOutsideAngular` allows you to escape Angular's zone and do work that + * doesn't trigger Angular change-detection or is subject to Angular's error handling. + * + * Any future tasks or microtasks scheduled from within this function will continue executing from + * outside of the Angular zone. + * + * Use {@link run} to reenter the Angular zone and do work that updates the application model. + */ + runOutsideAngular(fn: () => any): any { return this.outer.run(fn); } /** * Notifies when code enters Angular Zone. This gets fired first on VM Turn. @@ -185,51 +184,98 @@ export class NgZone { get onError(): EventEmitter { return this._onErrorEvents; } /** - * Whether there are no outstanding microtasks or microtasks. + * Whether there are no outstanding microtasks or macrotasks. */ get isStable(): boolean { return this._isStable; } - /** - * Whether there are any outstanding microtasks. - */ get hasPendingMicrotasks(): boolean { return this._hasPendingMicrotasks; } - /** - * Whether there are any outstanding microtasks. - */ get hasPendingMacrotasks(): boolean { return this._hasPendingMacrotasks; } - /** - * Executes the `fn` function synchronously within the Angular zone and returns value returned by - * the function. - * - * Running functions via `run` allows you to reenter Angular zone from a task that was executed - * outside of the Angular zone (typically started via {@link #runOutsideAngular}). - * - * Any future tasks or microtasks scheduled from within this function will continue executing from - * within the Angular zone. - * - * If a synchronous error happens it will be rethrown and not reported via `onError`. - */ - run(fn: () => any): any { return this._zoneImpl.runInner(fn); } + private checkStable() { + if (this._nesting == 0 && !this._hasPendingMicrotasks && !this._isStable) { + try { + this._nesting++; + this._onMicrotaskEmpty.emit(null); + } finally { + this._nesting--; + if (!this._hasPendingMicrotasks) { + try { + this.runOutsideAngular(() => this._onStable.emit(null)); + } finally { + this._isStable = true; + } + } + } + } + } - /** - * Same as #run, except that synchronous errors are caught and forwarded - * via `onError` and not rethrown. - */ - runGuarded(fn: () => any): any { return this._zoneImpl.runInnerGuarded(fn); } + private forkInnerZoneWithAngularBehavior() { + this.inner = this.inner.fork({ + name: 'angular', + properties: {'isAngularZone': true}, + onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, + applyThis: any, applyArgs: any): any => { + try { + this.onEnter(); + return delegate.invokeTask(target, task, applyThis, applyArgs); + } finally { + this.onLeave(); + } + }, - /** - * Executes the `fn` function synchronously in Angular's parent zone and returns value returned by - * the function. - * - * Running functions via `runOutsideAngular` allows you to escape Angular's zone and do work that - * doesn't trigger Angular change-detection or is subject to Angular's error handling. - * - * Any future tasks or microtasks scheduled from within this function will continue executing from - * outside of the Angular zone. - * - * Use {@link #run} to reenter the Angular zone and do work that updates the application model. - */ - runOutsideAngular(fn: () => any): any { return this._zoneImpl.runOuter(fn); } + + onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function, + applyThis: any, applyArgs: any[], source: string): any => { + try { + this.onEnter(); + return delegate.invoke(target, callback, applyThis, applyArgs, source); + } finally { + this.onLeave(); + } + }, + + onHasTask: + (delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => { + delegate.hasTask(target, hasTaskState); + if (current === target) { + // We are only interested in hasTask events which originate from our zone + // (A child hasTask event is not interesting to us) + if (hasTaskState.change == 'microTask') { + this.setHasMicrotask(hasTaskState.microTask); + } else if (hasTaskState.change == 'macroTask') { + this.setHasMacrotask(hasTaskState.macroTask); + } + } + }, + + onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => { + delegate.handleError(target, error); + this.triggerError(error); + return false; + } + }); + } + + private onEnter() { + this._nesting++; + if (this._isStable) { + this._isStable = false; + this._onUnstable.emit(null); + } + } + + private onLeave() { + this._nesting--; + this.checkStable(); + } + + private setHasMicrotask(hasMicrotasks: boolean) { + this._hasPendingMicrotasks = hasMicrotasks; + this.checkStable(); + } + + private setHasMacrotask(hasMacrotasks: boolean) { this._hasPendingMacrotasks = hasMacrotasks; } + + private triggerError(error: any) { this._onErrorEvents.emit(error); } } diff --git a/modules/@angular/core/src/zone/ng_zone_impl.ts b/modules/@angular/core/src/zone/ng_zone_impl.ts deleted file mode 100644 index 3d636e98ad..0000000000 --- a/modules/@angular/core/src/zone/ng_zone_impl.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - - -export class NgZoneImpl { - static isInAngularZone(): boolean { return Zone.current.get('isAngularZone') === true; } - - /** @internal */ - private outer: Zone; - /** @internal */ - private inner: Zone; - - private onEnter: () => void; - private onLeave: () => void; - private setMicrotask: (hasMicrotasks: boolean) => void; - private setMacrotask: (hasMacrotasks: boolean) => void; - private onError: (error: any) => void; - - constructor({trace, onEnter, onLeave, setMicrotask, setMacrotask, onError}: { - trace: boolean, - onEnter: () => void, - onLeave: () => void, - setMicrotask: (hasMicrotasks: boolean) => void, - setMacrotask: (hasMacrotasks: boolean) => void, - onError: (error: any) => void - }) { - this.onEnter = onEnter; - this.onLeave = onLeave; - this.setMicrotask = setMicrotask; - this.setMacrotask = setMacrotask; - this.onError = onError; - - if (typeof Zone == 'undefined') { - throw new Error('Angular requires Zone.js prolyfill.'); - } - Zone.assertZonePatched(); - this.outer = this.inner = Zone.current; - if ((Zone as any)['wtfZoneSpec']) { - this.inner = this.inner.fork((Zone as any)['wtfZoneSpec']); - } - if (trace && (Zone as any)['longStackTraceZoneSpec']) { - this.inner = this.inner.fork((Zone as any)['longStackTraceZoneSpec']); - } - this.inner = this.inner.fork({ - name: 'angular', - properties: {'isAngularZone': true}, - onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, - applyThis: any, applyArgs: any): any => { - try { - this.onEnter(); - return delegate.invokeTask(target, task, applyThis, applyArgs); - } finally { - this.onLeave(); - } - }, - - - onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function, - applyThis: any, applyArgs: any[], source: string): any => { - try { - this.onEnter(); - return delegate.invoke(target, callback, applyThis, applyArgs, source); - } finally { - this.onLeave(); - } - }, - - onHasTask: - (delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => { - delegate.hasTask(target, hasTaskState); - if (current === target) { - // We are only interested in hasTask events which originate from our zone - // (A child hasTask event is not interesting to us) - if (hasTaskState.change == 'microTask') { - this.setMicrotask(hasTaskState.microTask); - } else if (hasTaskState.change == 'macroTask') { - this.setMacrotask(hasTaskState.macroTask); - } - } - }, - - onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => { - delegate.handleError(target, error); - this.onError(error); - return false; - } - }); - } - - runInner(fn: () => any): any { return this.inner.run(fn); }; - runInnerGuarded(fn: () => any): any { return this.inner.runGuarded(fn); }; - runOuter(fn: () => any): any { return this.outer.run(fn); }; -}