feat(core): support for bootstrap with custom zone (#17672)
PR Close #17672
This commit is contained in:

committed by
Igor Minar

parent
6e1896b333
commit
344a5ca545
@ -28,7 +28,7 @@ import {InternalViewRef, ViewRef} from './linker/view_ref';
|
||||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
|
||||
import {Testability, TestabilityRegistry} from './testability/testability';
|
||||
import {Type} from './type';
|
||||
import {NgZone} from './zone/ng_zone';
|
||||
import {NgZone, NoopNgZone} from './zone/ng_zone';
|
||||
|
||||
let _devMode: boolean = true;
|
||||
let _runModeLocked: boolean = false;
|
||||
@ -158,6 +158,22 @@ export function getPlatform(): PlatformRef|null {
|
||||
return _platform && !_platform.destroyed ? _platform : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides additional options to the bootstraping process.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface BootstrapOptions {
|
||||
/**
|
||||
* Optionally specify which `NgZone` should be used.
|
||||
*
|
||||
* - Provide your own `NgZone` instance.
|
||||
* - `zone.js` - Use default `NgZone` which requires `Zone.js`.
|
||||
* - `noop` - Use `NoopNgZone` which does nothing.
|
||||
*/
|
||||
ngZone?: NgZone|'zone.js'|'noop';
|
||||
}
|
||||
|
||||
/**
|
||||
* The Angular platform is the entry point for Angular on a web page. Each page
|
||||
* has exactly one platform, and services (such as reflection) which are common
|
||||
@ -168,6 +184,7 @@ export function getPlatform(): PlatformRef|null {
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
export class PlatformRef {
|
||||
private _modules: NgModuleRef<any>[] = [];
|
||||
private _destroyListeners: Function[] = [];
|
||||
@ -199,17 +216,14 @@ export class PlatformRef {
|
||||
*
|
||||
* @experimental APIs related to application bootstrap are currently under review.
|
||||
*/
|
||||
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>> {
|
||||
return this._bootstrapModuleFactoryWithZone(moduleFactory);
|
||||
}
|
||||
|
||||
private _bootstrapModuleFactoryWithZone<M>(moduleFactory: NgModuleFactory<M>, ngZone?: NgZone):
|
||||
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):
|
||||
Promise<NgModuleRef<M>> {
|
||||
// Note: We need to create the NgZone _before_ we instantiate the module,
|
||||
// as instantiating the module creates some providers eagerly.
|
||||
// So we create a mini parent injector that just contains the new NgZone and
|
||||
// pass that as parent to the NgModuleFactory.
|
||||
if (!ngZone) ngZone = new NgZone({enableLongStackTrace: isDevMode()});
|
||||
const ngZoneOption = options ? options.ngZone : undefined;
|
||||
const ngZone = getNgZone(ngZoneOption);
|
||||
// Attention: Don't use ApplicationRef.run here,
|
||||
// as we want to be sure that all possible constructor calls are inside `ngZone.run`!
|
||||
return ngZone.run(() => {
|
||||
@ -249,20 +263,15 @@ export class PlatformRef {
|
||||
* ```
|
||||
* @stable
|
||||
*/
|
||||
bootstrapModule<M>(moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = []):
|
||||
Promise<NgModuleRef<M>> {
|
||||
return this._bootstrapModuleWithZone(moduleType, compilerOptions);
|
||||
}
|
||||
|
||||
private _bootstrapModuleWithZone<M>(
|
||||
moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = [],
|
||||
ngZone?: NgZone): Promise<NgModuleRef<M>> {
|
||||
bootstrapModule<M>(
|
||||
moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)|
|
||||
Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> {
|
||||
const compilerFactory: CompilerFactory = this.injector.get(CompilerFactory);
|
||||
const compiler = compilerFactory.createCompiler(
|
||||
Array.isArray(compilerOptions) ? compilerOptions : [compilerOptions]);
|
||||
const options = optionsReducer({}, compilerOptions);
|
||||
const compiler = compilerFactory.createCompiler([options]);
|
||||
|
||||
return compiler.compileModuleAsync(moduleType)
|
||||
.then((moduleFactory) => this._bootstrapModuleFactoryWithZone(moduleFactory, ngZone));
|
||||
.then((moduleFactory) => this.bootstrapModuleFactory(moduleFactory, options));
|
||||
}
|
||||
|
||||
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
|
||||
@ -305,6 +314,18 @@ export class PlatformRef {
|
||||
get destroyed() { return this._destroyed; }
|
||||
}
|
||||
|
||||
function getNgZone(ngZoneOption?: NgZone | 'zone.js' | 'noop'): NgZone {
|
||||
let ngZone: NgZone;
|
||||
|
||||
if (ngZoneOption === 'noop') {
|
||||
ngZone = new NoopNgZone();
|
||||
} else {
|
||||
ngZone = (ngZoneOption === 'zone.js' ? undefined : ngZoneOption) ||
|
||||
new NgZone({enableLongStackTrace: isDevMode()});
|
||||
}
|
||||
return ngZone;
|
||||
}
|
||||
|
||||
function _callAndReportToErrorHandler(
|
||||
errorHandler: ErrorHandler, ngZone: NgZone, callback: () => any): any {
|
||||
try {
|
||||
@ -325,6 +346,15 @@ function _callAndReportToErrorHandler(
|
||||
}
|
||||
}
|
||||
|
||||
function optionsReducer<T extends Object>(dst: any, objs: T | T[]): T {
|
||||
if (Array.isArray(objs)) {
|
||||
dst = objs.reduce(optionsReducer, dst);
|
||||
} else {
|
||||
dst = {...dst, ...(objs as any)};
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to an Angular application running on a page.
|
||||
*
|
||||
|
@ -98,7 +98,7 @@ export class NgZone {
|
||||
readonly onUnstable: EventEmitter<any> = new EventEmitter(false);
|
||||
|
||||
/**
|
||||
* Notifies when there is no more microtasks enqueue in the current VM Turn.
|
||||
* Notifies when there is no more microtasks enqueued in the current VM Turn.
|
||||
* This is a hint for Angular to do change detection, which may enqueue more microtasks.
|
||||
* For this reason this event can fire multiple times per VM Turn.
|
||||
*/
|
||||
@ -216,7 +216,7 @@ export class NgZone {
|
||||
}
|
||||
}
|
||||
|
||||
function noop(){};
|
||||
function noop() {}
|
||||
const EMPTY_PAYLOAD = {};
|
||||
|
||||
|
||||
@ -308,3 +308,27 @@ function onLeave(zone: NgZonePrivate) {
|
||||
zone._nesting--;
|
||||
checkStable(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a noop implementation of `NgZone` which does nothing. This zone requires explicit calls
|
||||
* to framework to perform rendering.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class NoopNgZone implements NgZone {
|
||||
readonly hasPendingMicrotasks: boolean = false;
|
||||
readonly hasPendingMacrotasks: boolean = false;
|
||||
readonly isStable: boolean = true;
|
||||
readonly onUnstable: EventEmitter<any> = new EventEmitter();
|
||||
readonly onMicrotaskEmpty: EventEmitter<any> = new EventEmitter();
|
||||
readonly onStable: EventEmitter<any> = new EventEmitter();
|
||||
readonly onError: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
run(fn: () => any): any { return fn(); }
|
||||
|
||||
runGuarded(fn: () => any): any { return fn(); }
|
||||
|
||||
runOutsideAngular(fn: () => any): any { return fn(); }
|
||||
|
||||
runTask<T>(fn: () => any): any { return fn(); }
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ServerModule} from '@angular/platform-server';
|
||||
|
||||
import {ComponentFixture, ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
|
||||
import {NoopNgZone} from '../src/zone/ng_zone';
|
||||
import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
|
||||
|
||||
@Component({selector: 'bootstrap-app', template: 'hello'})
|
||||
class SomeComponent {
|
||||
@ -287,6 +287,15 @@ export function main() {
|
||||
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
|
||||
.then(module => expect((<any>defaultPlatform)._modules).toContain(module));
|
||||
}));
|
||||
|
||||
it('should bootstrap with NoopNgZone', async(() => {
|
||||
defaultPlatform
|
||||
.bootstrapModule(createModule({bootstrap: [SomeComponent]}), {ngZone: 'noop'})
|
||||
.then((module) => {
|
||||
const ngZone = module.injector.get(NgZone);
|
||||
expect(ngZone instanceof NoopNgZone).toBe(true);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('bootstrapModuleFactory', () => {
|
||||
|
@ -6,11 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgZone} from '@angular/core/src/zone/ng_zone';
|
||||
import {EventEmitter, NgZone} from '@angular/core';
|
||||
import {async, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, Log, beforeEach, describe, expect, inject, it, xit} from '@angular/core/testing/src/testing_internal';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {scheduleMicroTask} from '../../src/util';
|
||||
import {NoopNgZone} from '../../src/zone/ng_zone';
|
||||
|
||||
const needsLongerTimers = browserDetection.isSlow || browserDetection.isEdge;
|
||||
const resultTimer = 1000;
|
||||
@ -170,6 +171,25 @@ export function main() {
|
||||
}), testTimeout);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NoopNgZone', () => {
|
||||
const ngZone = new NoopNgZone();
|
||||
|
||||
it('should run', () => {
|
||||
let runs = false;
|
||||
ngZone.run(() => {
|
||||
ngZone.runGuarded(() => { ngZone.runOutsideAngular(() => { runs = true; }); });
|
||||
});
|
||||
expect(runs).toBe(true);
|
||||
});
|
||||
|
||||
it('should have EventEmitter instances', () => {
|
||||
expect(ngZone.onError instanceof EventEmitter).toBe(true);
|
||||
expect(ngZone.onStable instanceof EventEmitter).toBe(true);
|
||||
expect(ngZone.onUnstable instanceof EventEmitter).toBe(true);
|
||||
expect(ngZone.onMicrotaskEmpty instanceof EventEmitter).toBe(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function commonTests() {
|
||||
|
Reference in New Issue
Block a user