feat(core): support for bootstrap with custom zone (#17672)
PR Close #17672
This commit is contained in:
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() {
|
||||
|
|
|
@ -572,9 +572,9 @@ export class UpgradeAdapter {
|
|||
constructor() {}
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
(platformRef as any)
|
||||
._bootstrapModuleWithZone(
|
||||
DynamicNgUpgradeModule, this.compilerOptions, this.ngZone)
|
||||
platformRef
|
||||
.bootstrapModule(
|
||||
DynamicNgUpgradeModule, [this.compilerOptions !, {ngZone: this.ngZone}])
|
||||
.then((ref: NgModuleRef<any>) => {
|
||||
this.moduleRef = ref;
|
||||
this.ngZone.run(() => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgZone, OnChanges, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
|
||||
import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
|
@ -56,7 +56,7 @@ export function main() {
|
|||
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
||||
})
|
||||
class Ng2 {
|
||||
};
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
||||
|
@ -80,7 +80,8 @@ export function main() {
|
|||
|
||||
it('supports the compilerOptions argument', async(() => {
|
||||
const platformRef = platformBrowserDynamic();
|
||||
spyOn(platformRef, '_bootstrapModuleWithZone').and.callThrough();
|
||||
spyOn(platformRef, 'bootstrapModule').and.callThrough();
|
||||
spyOn(platformRef, 'bootstrapModuleFactory').and.callThrough();
|
||||
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
@Component({selector: 'ng2', template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
||||
|
@ -96,13 +97,17 @@ export function main() {
|
|||
})
|
||||
class Ng2AppModule {
|
||||
ngDoBootstrap() {}
|
||||
};
|
||||
}
|
||||
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2AppModule, {providers: []});
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect((platformRef as any)._bootstrapModuleWithZone)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function), {providers: []}, jasmine.any(Object));
|
||||
expect(platformRef.bootstrapModule).toHaveBeenCalledWith(jasmine.any(Function), [
|
||||
{providers: []}, jasmine.any(Object)
|
||||
]);
|
||||
expect(platformRef.bootstrapModuleFactory)
|
||||
.toHaveBeenCalledWith(
|
||||
jasmine.any(NgModuleFactory), {providers: [], ngZone: jasmine.any(NgZone)});
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
@ -395,7 +400,7 @@ export function main() {
|
|||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
};
|
||||
}
|
||||
|
||||
const element = html(`<div>
|
||||
<ng2 literal="Text" interpolate="Hello {{name}}"
|
||||
|
|
|
@ -698,8 +698,8 @@ export declare const platformCore: (extraProviders?: StaticProvider[] | undefine
|
|||
export declare class PlatformRef {
|
||||
readonly destroyed: boolean;
|
||||
readonly injector: Injector;
|
||||
/** @stable */ bootstrapModule<M>(moduleType: Type<M>, compilerOptions?: CompilerOptions | CompilerOptions[]): Promise<NgModuleRef<M>>;
|
||||
/** @experimental */ bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>>;
|
||||
/** @stable */ bootstrapModule<M>(moduleType: Type<M>, compilerOptions?: (CompilerOptions & BootstrapOptions) | Array<CompilerOptions & BootstrapOptions>): Promise<NgModuleRef<M>>;
|
||||
/** @experimental */ bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions): Promise<NgModuleRef<M>>;
|
||||
destroy(): void;
|
||||
onDestroy(callback: () => void): void;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue