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 {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
|
||||||
import {Testability, TestabilityRegistry} from './testability/testability';
|
import {Testability, TestabilityRegistry} from './testability/testability';
|
||||||
import {Type} from './type';
|
import {Type} from './type';
|
||||||
import {NgZone} from './zone/ng_zone';
|
import {NgZone, NoopNgZone} from './zone/ng_zone';
|
||||||
|
|
||||||
let _devMode: boolean = true;
|
let _devMode: boolean = true;
|
||||||
let _runModeLocked: boolean = false;
|
let _runModeLocked: boolean = false;
|
||||||
@ -158,6 +158,22 @@ export function getPlatform(): PlatformRef|null {
|
|||||||
return _platform && !_platform.destroyed ? _platform : 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
|
* 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
|
* has exactly one platform, and services (such as reflection) which are common
|
||||||
@ -168,6 +184,7 @@ export function getPlatform(): PlatformRef|null {
|
|||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
|
@Injectable()
|
||||||
export class PlatformRef {
|
export class PlatformRef {
|
||||||
private _modules: NgModuleRef<any>[] = [];
|
private _modules: NgModuleRef<any>[] = [];
|
||||||
private _destroyListeners: Function[] = [];
|
private _destroyListeners: Function[] = [];
|
||||||
@ -199,17 +216,14 @@ export class PlatformRef {
|
|||||||
*
|
*
|
||||||
* @experimental APIs related to application bootstrap are currently under review.
|
* @experimental APIs related to application bootstrap are currently under review.
|
||||||
*/
|
*/
|
||||||
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>> {
|
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):
|
||||||
return this._bootstrapModuleFactoryWithZone(moduleFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _bootstrapModuleFactoryWithZone<M>(moduleFactory: NgModuleFactory<M>, ngZone?: NgZone):
|
|
||||||
Promise<NgModuleRef<M>> {
|
Promise<NgModuleRef<M>> {
|
||||||
// Note: We need to create the NgZone _before_ we instantiate the module,
|
// Note: We need to create the NgZone _before_ we instantiate the module,
|
||||||
// as instantiating the module creates some providers eagerly.
|
// as instantiating the module creates some providers eagerly.
|
||||||
// So we create a mini parent injector that just contains the new NgZone and
|
// So we create a mini parent injector that just contains the new NgZone and
|
||||||
// pass that as parent to the NgModuleFactory.
|
// 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,
|
// Attention: Don't use ApplicationRef.run here,
|
||||||
// as we want to be sure that all possible constructor calls are inside `ngZone.run`!
|
// as we want to be sure that all possible constructor calls are inside `ngZone.run`!
|
||||||
return ngZone.run(() => {
|
return ngZone.run(() => {
|
||||||
@ -249,20 +263,15 @@ export class PlatformRef {
|
|||||||
* ```
|
* ```
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
bootstrapModule<M>(moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = []):
|
bootstrapModule<M>(
|
||||||
Promise<NgModuleRef<M>> {
|
moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)|
|
||||||
return this._bootstrapModuleWithZone(moduleType, compilerOptions);
|
Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> {
|
||||||
}
|
|
||||||
|
|
||||||
private _bootstrapModuleWithZone<M>(
|
|
||||||
moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = [],
|
|
||||||
ngZone?: NgZone): Promise<NgModuleRef<M>> {
|
|
||||||
const compilerFactory: CompilerFactory = this.injector.get(CompilerFactory);
|
const compilerFactory: CompilerFactory = this.injector.get(CompilerFactory);
|
||||||
const compiler = compilerFactory.createCompiler(
|
const options = optionsReducer({}, compilerOptions);
|
||||||
Array.isArray(compilerOptions) ? compilerOptions : [compilerOptions]);
|
const compiler = compilerFactory.createCompiler([options]);
|
||||||
|
|
||||||
return compiler.compileModuleAsync(moduleType)
|
return compiler.compileModuleAsync(moduleType)
|
||||||
.then((moduleFactory) => this._bootstrapModuleFactoryWithZone(moduleFactory, ngZone));
|
.then((moduleFactory) => this.bootstrapModuleFactory(moduleFactory, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
|
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
|
||||||
@ -305,6 +314,18 @@ export class PlatformRef {
|
|||||||
get destroyed() { return this._destroyed; }
|
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(
|
function _callAndReportToErrorHandler(
|
||||||
errorHandler: ErrorHandler, ngZone: NgZone, callback: () => any): any {
|
errorHandler: ErrorHandler, ngZone: NgZone, callback: () => any): any {
|
||||||
try {
|
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.
|
* A reference to an Angular application running on a page.
|
||||||
*
|
*
|
||||||
|
@ -98,7 +98,7 @@ export class NgZone {
|
|||||||
readonly onUnstable: EventEmitter<any> = new EventEmitter(false);
|
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.
|
* 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.
|
* 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 = {};
|
const EMPTY_PAYLOAD = {};
|
||||||
|
|
||||||
|
|
||||||
@ -308,3 +308,27 @@ function onLeave(zone: NgZonePrivate) {
|
|||||||
zone._nesting--;
|
zone._nesting--;
|
||||||
checkStable(zone);
|
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 {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
import {ServerModule} from '@angular/platform-server';
|
import {ServerModule} from '@angular/platform-server';
|
||||||
|
import {NoopNgZone} from '../src/zone/ng_zone';
|
||||||
import {ComponentFixture, ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
|
import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
|
||||||
|
|
||||||
@Component({selector: 'bootstrap-app', template: 'hello'})
|
@Component({selector: 'bootstrap-app', template: 'hello'})
|
||||||
class SomeComponent {
|
class SomeComponent {
|
||||||
@ -287,6 +287,15 @@ export function main() {
|
|||||||
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
|
defaultPlatform.bootstrapModule(createModule({bootstrap: [SomeComponent]}))
|
||||||
.then(module => expect((<any>defaultPlatform)._modules).toContain(module));
|
.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', () => {
|
describe('bootstrapModuleFactory', () => {
|
||||||
|
@ -6,11 +6,12 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {async, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||||
import {AsyncTestCompleter, Log, beforeEach, describe, expect, inject, it, xit} from '@angular/core/testing/src/testing_internal';
|
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 {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
||||||
import {scheduleMicroTask} from '../../src/util';
|
import {scheduleMicroTask} from '../../src/util';
|
||||||
|
import {NoopNgZone} from '../../src/zone/ng_zone';
|
||||||
|
|
||||||
const needsLongerTimers = browserDetection.isSlow || browserDetection.isEdge;
|
const needsLongerTimers = browserDetection.isSlow || browserDetection.isEdge;
|
||||||
const resultTimer = 1000;
|
const resultTimer = 1000;
|
||||||
@ -170,6 +171,25 @@ export function main() {
|
|||||||
}), testTimeout);
|
}), 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() {
|
function commonTests() {
|
||||||
|
@ -572,9 +572,9 @@ export class UpgradeAdapter {
|
|||||||
constructor() {}
|
constructor() {}
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
(platformRef as any)
|
platformRef
|
||||||
._bootstrapModuleWithZone(
|
.bootstrapModule(
|
||||||
DynamicNgUpgradeModule, this.compilerOptions, this.ngZone)
|
DynamicNgUpgradeModule, [this.compilerOptions !, {ngZone: this.ngZone}])
|
||||||
.then((ref: NgModuleRef<any>) => {
|
.then((ref: NgModuleRef<any>) => {
|
||||||
this.moduleRef = ref;
|
this.moduleRef = ref;
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
@ -56,7 +56,7 @@ export function main() {
|
|||||||
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
||||||
})
|
})
|
||||||
class Ng2 {
|
class Ng2 {
|
||||||
};
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
||||||
@ -80,7 +80,8 @@ export function main() {
|
|||||||
|
|
||||||
it('supports the compilerOptions argument', async(() => {
|
it('supports the compilerOptions argument', async(() => {
|
||||||
const platformRef = platformBrowserDynamic();
|
const platformRef = platformBrowserDynamic();
|
||||||
spyOn(platformRef, '_bootstrapModuleWithZone').and.callThrough();
|
spyOn(platformRef, 'bootstrapModule').and.callThrough();
|
||||||
|
spyOn(platformRef, 'bootstrapModuleFactory').and.callThrough();
|
||||||
|
|
||||||
const ng1Module = angular.module('ng1', []);
|
const ng1Module = angular.module('ng1', []);
|
||||||
@Component({selector: 'ng2', template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
@Component({selector: 'ng2', template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
||||||
@ -96,13 +97,17 @@ export function main() {
|
|||||||
})
|
})
|
||||||
class Ng2AppModule {
|
class Ng2AppModule {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
};
|
}
|
||||||
|
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2AppModule, {providers: []});
|
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2AppModule, {providers: []});
|
||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
expect((platformRef as any)._bootstrapModuleWithZone)
|
expect(platformRef.bootstrapModule).toHaveBeenCalledWith(jasmine.any(Function), [
|
||||||
.toHaveBeenCalledWith(jasmine.any(Function), {providers: []}, jasmine.any(Object));
|
{providers: []}, jasmine.any(Object)
|
||||||
|
]);
|
||||||
|
expect(platformRef.bootstrapModuleFactory)
|
||||||
|
.toHaveBeenCalledWith(
|
||||||
|
jasmine.any(NgModuleFactory), {providers: [], ngZone: jasmine.any(NgZone)});
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
@ -395,7 +400,7 @@ export function main() {
|
|||||||
imports: [BrowserModule],
|
imports: [BrowserModule],
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
};
|
}
|
||||||
|
|
||||||
const element = html(`<div>
|
const element = html(`<div>
|
||||||
<ng2 literal="Text" interpolate="Hello {{name}}"
|
<ng2 literal="Text" interpolate="Hello {{name}}"
|
||||||
|
4
tools/public_api_guard/core/core.d.ts
vendored
4
tools/public_api_guard/core/core.d.ts
vendored
@ -698,8 +698,8 @@ export declare const platformCore: (extraProviders?: StaticProvider[] | undefine
|
|||||||
export declare class PlatformRef {
|
export declare class PlatformRef {
|
||||||
readonly destroyed: boolean;
|
readonly destroyed: boolean;
|
||||||
readonly injector: Injector;
|
readonly injector: Injector;
|
||||||
/** @stable */ bootstrapModule<M>(moduleType: Type<M>, compilerOptions?: CompilerOptions | CompilerOptions[]): Promise<NgModuleRef<M>>;
|
/** @stable */ bootstrapModule<M>(moduleType: Type<M>, compilerOptions?: (CompilerOptions & BootstrapOptions) | Array<CompilerOptions & BootstrapOptions>): Promise<NgModuleRef<M>>;
|
||||||
/** @experimental */ bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>>;
|
/** @experimental */ bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions): Promise<NgModuleRef<M>>;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
onDestroy(callback: () => void): void;
|
onDestroy(callback: () => void): void;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user