angular-cn/packages/core/src/application_ref.ts

572 lines
20 KiB
TypeScript
Raw Normal View History

/**
* @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
*/
import {Observable, Observer, Subscription, merge} from 'rxjs';
import {share} from 'rxjs/operators';
import {ApplicationInitStatus} from './application_init';
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
import {Console} from './console';
import {Injectable, InjectionToken, Injector, StaticProvider} from './di';
import {ErrorHandler} from './error_handler';
import {isDevMode} from './is_dev_mode';
2016-08-11 18:24:35 -04:00
import {CompilerFactory, CompilerOptions} from './linker/compiler';
import {ComponentFactory, ComponentRef} from './linker/component_factory';
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from './linker/component_factory_resolver';
import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from './linker/ng_module_factory';
import {InternalViewRef, ViewRef} from './linker/view_ref';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
import {assertNgModuleType} from './render3/assert';
import {NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref';
import {Testability, TestabilityRegistry} from './testability/testability';
import {Type} from './type';
import {scheduleMicroTask, stringify} from './util';
import {isPromise} from './util/lang';
import {NgZone, NoopNgZone} from './zone/ng_zone';
2016-09-20 17:14:57 -04:00
let _platform: PlatformRef;
let compileNgModuleFactory:
<M>(injector: Injector, options: CompilerOptions, moduleType: Type<M>) =>
Promise<NgModuleFactory<M>> = compileNgModuleFactory__PRE_NGCC__;
function compileNgModuleFactory__PRE_NGCC__<M>(
injector: Injector, options: CompilerOptions,
moduleType: Type<M>): Promise<NgModuleFactory<M>> {
const compilerFactory: CompilerFactory = injector.get(CompilerFactory);
const compiler = compilerFactory.createCompiler([options]);
return compiler.compileModuleAsync(moduleType);
}
export function compileNgModuleFactory__POST_NGCC__<M>(
injector: Injector, options: CompilerOptions,
moduleType: Type<M>): Promise<NgModuleFactory<M>> {
ngDevMode && assertNgModuleType(moduleType);
return Promise.resolve(new R3NgModuleFactory(moduleType));
}
export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken<boolean>('AllowMultipleToken');
/**
* A token for third-party components that can register themselves with NgProbe.
*
* @experimental
*/
export class NgProbeToken {
constructor(public name: string, public token: any) {}
}
/**
* Creates a platform.
* Platforms have to be eagerly created via this function.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function createPlatform(injector: Injector): PlatformRef {
if (_platform && !_platform.destroyed &&
!_platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
throw new Error(
'There can be only one platform. Destroy the previous one to create a new one.');
}
_platform = injector.get(PlatformRef);
feat(core): Add type information to injector.get() (#13785) - Introduce `InjectionToken<T>` which is a parameterized and type-safe version of `OpaqueToken`. DEPRECATION: - `OpaqueToken` is now deprecated, use `InjectionToken<T>` instead. - `Injector.get(token: any, notFoundValue?: any): any` is now deprecated use the same method which is now overloaded as `Injector.get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;`. Migration - Replace `OpaqueToken` with `InjectionToken<?>` and parameterize it. - Migrate your code to only use `Type<?>` or `InjectionToken<?>` as injection tokens. Using other tokens will not be supported in the future. BREAKING CHANGE: - Because `injector.get()` is now parameterize it is possible that code which used to work no longer type checks. Example would be if one injects `Foo` but configures it as `{provide: Foo, useClass: MockFoo}`. The injection instance will be that of `MockFoo` but the type will be `Foo` instead of `any` as in the past. This means that it was possible to call a method on `MockFoo` in the past which now will fail type check. See this example: ``` class Foo {} class MockFoo extends Foo { setupMock(); } var PROVIDERS = [ {provide: Foo, useClass: MockFoo} ]; ... function myTest(injector: Injector) { var foo = injector.get(Foo); // This line used to work since `foo` used to be `any` before this // change, it will now be `Foo`, and `Foo` does not have `setUpMock()`. // The fix is to downcast: `injector.get(Foo) as MockFoo`. foo.setUpMock(); } ``` PR Close #13785
2017-01-03 19:54:46 -05:00
const inits = injector.get(PLATFORM_INITIALIZER, null);
if (inits) inits.forEach((init: any) => init());
return _platform;
}
refactor(core): clean up platform bootstrap and initTestEnvironment - Introduces `CompilerFactory` which can be part of a `PlatformRef`. - Introduces `WorkerAppModule`, `WorkerUiModule`, `ServerModule` - Introduces `serverDynamicPlatform` for applications using runtime compilation on the server. - Changes browser bootstrap for runtime and offline compilation (see below for an example). * introduces `bootstrapModule` and `bootstrapModuleFactory` in `@angular/core` * introduces new `browserDynamicPlatform` in `@angular/platform-browser-dynamic - Changes `initTestEnvironment` (which used to be `setBaseTestProviders`) to not take a compiler factory any more (see below for an example). BREAKING CHANGE: ## Migration from `setBaseTestProviders` to `initTestEnvironment`: - For the browser platform: BEFORE: ``` import {setBaseTestProviders} from ‘@angular/core/testing’; import {TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS} from ‘@angular/platform-browser-dynamic/testing’; setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS); ``` AFTER: ``` import {initTestEnvironment} from ‘@angular/core/testing’; import {browserDynamicTestPlatform, BrowserDynamicTestModule} from ‘@angular/platform-browser-dynamic/testing’; initTestEnvironment( BrowserDynamicTestModule, browserDynamicTestPlatform()); ``` - For the server platform: BEFORE: ``` import {setBaseTestProviders} from ‘@angular/core/testing’; import {TEST_SERVER_PLATFORM_PROVIDERS, TEST_SERVER_APPLICATION_PROVIDERS} from ‘@angular/platform-server/testing/server’; setBaseTestProviders(TEST_SERVER_PLATFORM_PROVIDERS, TEST_SERVER_APPLICATION_PROVIDERS); ``` AFTER: ``` import {initTestEnvironment} from ‘@angular/core/testing’; import {serverTestPlatform, ServerTestModule} from ‘@angular/platform-browser-dynamic/testing’; initTestEnvironment( ServerTestModule, serverTestPlatform()); ``` ## Bootstrap changes ``` @AppModule({ modules: [BrowserModule], precompile: [MainComponent], providers: […], // additional providers directives: […], // additional platform directives pipes: […] // additional platform pipes }) class MyModule { constructor(appRef: ApplicationRef) { appRef.bootstrap(MainComponent); } } // offline compile import {browserPlatform} from ‘@angular/platform-browser’; import {bootstrapModuleFactory} from ‘@angular/core’; bootstrapModuleFactory(MyModuleNgFactory, browserPlatform()); // runtime compile long form import {browserDynamicPlatform} from ‘@angular/platform-browser-dynamic’; import {bootstrapModule} from ‘@angular/core’; bootstrapModule(MyModule, browserDynamicPlatform()); ``` Closes #9922 Part of #9726
2016-07-08 13:47:17 -04:00
/**
* Creates a factory for a platform
refactor(core): clean up platform bootstrap and initTestEnvironment - Introduces `CompilerFactory` which can be part of a `PlatformRef`. - Introduces `WorkerAppModule`, `WorkerUiModule`, `ServerModule` - Introduces `serverDynamicPlatform` for applications using runtime compilation on the server. - Changes browser bootstrap for runtime and offline compilation (see below for an example). * introduces `bootstrapModule` and `bootstrapModuleFactory` in `@angular/core` * introduces new `browserDynamicPlatform` in `@angular/platform-browser-dynamic - Changes `initTestEnvironment` (which used to be `setBaseTestProviders`) to not take a compiler factory any more (see below for an example). BREAKING CHANGE: ## Migration from `setBaseTestProviders` to `initTestEnvironment`: - For the browser platform: BEFORE: ``` import {setBaseTestProviders} from ‘@angular/core/testing’; import {TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS} from ‘@angular/platform-browser-dynamic/testing’; setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS); ``` AFTER: ``` import {initTestEnvironment} from ‘@angular/core/testing’; import {browserDynamicTestPlatform, BrowserDynamicTestModule} from ‘@angular/platform-browser-dynamic/testing’; initTestEnvironment( BrowserDynamicTestModule, browserDynamicTestPlatform()); ``` - For the server platform: BEFORE: ``` import {setBaseTestProviders} from ‘@angular/core/testing’; import {TEST_SERVER_PLATFORM_PROVIDERS, TEST_SERVER_APPLICATION_PROVIDERS} from ‘@angular/platform-server/testing/server’; setBaseTestProviders(TEST_SERVER_PLATFORM_PROVIDERS, TEST_SERVER_APPLICATION_PROVIDERS); ``` AFTER: ``` import {initTestEnvironment} from ‘@angular/core/testing’; import {serverTestPlatform, ServerTestModule} from ‘@angular/platform-browser-dynamic/testing’; initTestEnvironment( ServerTestModule, serverTestPlatform()); ``` ## Bootstrap changes ``` @AppModule({ modules: [BrowserModule], precompile: [MainComponent], providers: […], // additional providers directives: […], // additional platform directives pipes: […] // additional platform pipes }) class MyModule { constructor(appRef: ApplicationRef) { appRef.bootstrap(MainComponent); } } // offline compile import {browserPlatform} from ‘@angular/platform-browser’; import {bootstrapModuleFactory} from ‘@angular/core’; bootstrapModuleFactory(MyModuleNgFactory, browserPlatform()); // runtime compile long form import {browserDynamicPlatform} from ‘@angular/platform-browser-dynamic’; import {bootstrapModule} from ‘@angular/core’; bootstrapModule(MyModule, browserDynamicPlatform()); ``` Closes #9922 Part of #9726
2016-07-08 13:47:17 -04:00
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function createPlatformFactory(
parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null,
name: string, providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) =>
PlatformRef {
const desc = `Platform: ${name}`;
const marker = new InjectionToken(desc);
return (extraProviders: StaticProvider[] = []) => {
let platform = getPlatform();
if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
if (parentPlatformFactory) {
parentPlatformFactory(
providers.concat(extraProviders).concat({provide: marker, useValue: true}));
} else {
const injectedProviders: StaticProvider[] =
providers.concat(extraProviders).concat({provide: marker, useValue: true});
createPlatform(Injector.create({providers: injectedProviders, name: desc}));
}
refactor(core): clean up platform bootstrap and initTestEnvironment - Introduces `CompilerFactory` which can be part of a `PlatformRef`. - Introduces `WorkerAppModule`, `WorkerUiModule`, `ServerModule` - Introduces `serverDynamicPlatform` for applications using runtime compilation on the server. - Changes browser bootstrap for runtime and offline compilation (see below for an example). * introduces `bootstrapModule` and `bootstrapModuleFactory` in `@angular/core` * introduces new `browserDynamicPlatform` in `@angular/platform-browser-dynamic - Changes `initTestEnvironment` (which used to be `setBaseTestProviders`) to not take a compiler factory any more (see below for an example). BREAKING CHANGE: ## Migration from `setBaseTestProviders` to `initTestEnvironment`: - For the browser platform: BEFORE: ``` import {setBaseTestProviders} from ‘@angular/core/testing’; import {TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS} from ‘@angular/platform-browser-dynamic/testing’; setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS); ``` AFTER: ``` import {initTestEnvironment} from ‘@angular/core/testing’; import {browserDynamicTestPlatform, BrowserDynamicTestModule} from ‘@angular/platform-browser-dynamic/testing’; initTestEnvironment( BrowserDynamicTestModule, browserDynamicTestPlatform()); ``` - For the server platform: BEFORE: ``` import {setBaseTestProviders} from ‘@angular/core/testing’; import {TEST_SERVER_PLATFORM_PROVIDERS, TEST_SERVER_APPLICATION_PROVIDERS} from ‘@angular/platform-server/testing/server’; setBaseTestProviders(TEST_SERVER_PLATFORM_PROVIDERS, TEST_SERVER_APPLICATION_PROVIDERS); ``` AFTER: ``` import {initTestEnvironment} from ‘@angular/core/testing’; import {serverTestPlatform, ServerTestModule} from ‘@angular/platform-browser-dynamic/testing’; initTestEnvironment( ServerTestModule, serverTestPlatform()); ``` ## Bootstrap changes ``` @AppModule({ modules: [BrowserModule], precompile: [MainComponent], providers: […], // additional providers directives: […], // additional platform directives pipes: […] // additional platform pipes }) class MyModule { constructor(appRef: ApplicationRef) { appRef.bootstrap(MainComponent); } } // offline compile import {browserPlatform} from ‘@angular/platform-browser’; import {bootstrapModuleFactory} from ‘@angular/core’; bootstrapModuleFactory(MyModuleNgFactory, browserPlatform()); // runtime compile long form import {browserDynamicPlatform} from ‘@angular/platform-browser-dynamic’; import {bootstrapModule} from ‘@angular/core’; bootstrapModule(MyModule, browserDynamicPlatform()); ``` Closes #9922 Part of #9726
2016-07-08 13:47:17 -04:00
}
return assertPlatform(marker);
};
}
/**
* Checks that there currently is a platform which contains the given token as a provider.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function assertPlatform(requiredToken: any): PlatformRef {
2016-09-20 17:14:57 -04:00
const platform = getPlatform();
if (!platform) {
throw new Error('No platform exists!');
}
2016-09-20 17:14:57 -04:00
if (!platform.injector.get(requiredToken, null)) {
throw new Error(
'A platform with a different configuration has been created. Please destroy it first.');
}
2016-09-20 17:14:57 -04:00
return platform;
}
/**
* Destroy the existing platform.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function destroyPlatform(): void {
2016-09-20 17:14:57 -04:00
if (_platform && !_platform.destroyed) {
_platform.destroy();
}
}
/**
* Returns the current platform.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function getPlatform(): PlatformRef|null {
2016-09-20 17:14:57 -04:00
return _platform && !_platform.destroyed ? _platform : null;
}
/**
* Provides additional options to the bootstraping process.
*
*
*/
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
* to every Angular application running on the page are bound in its scope.
*
2017-04-24 20:15:33 -04:00
* A page's platform is initialized implicitly when a platform is created via a platform factory
* (e.g. {@link platformBrowser}), or explicitly by calling the {@link createPlatform} function.
*/
@Injectable()
export class PlatformRef {
private _modules: NgModuleRef<any>[] = [];
private _destroyListeners: Function[] = [];
private _destroyed: boolean = false;
/** @internal */
constructor(private _injector: Injector) {}
/**
* Creates an instance of an `@NgModule` for the given platform
* for offline compilation.
*
* @usageNotes
* ### Simple Example
*
* ```typescript
* my_module.ts:
*
* @NgModule({
* imports: [BrowserModule]
* })
* class MyModule {}
*
* main.ts:
* import {MyModuleNgFactory} from './my_module.ngfactory';
* import {platformBrowser} from '@angular/platform-browser';
*
* let moduleRef = platformBrowser().bootstrapModuleFactory(MyModuleNgFactory);
* ```
*
* @experimental APIs related to application bootstrap are currently under review.
*/
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.
const ngZoneOption = options ? options.ngZone : undefined;
const ngZone = getNgZone(ngZoneOption);
const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}];
// 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(() => {
const ngZoneInjector = Injector.create(
{providers: providers, parent: this.injector, name: moduleFactory.moduleType.name});
const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);
const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
if (!exceptionHandler) {
throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
}
moduleRef.onDestroy(() => remove(this._modules, moduleRef));
ngZone !.runOutsideAngular(
() => ngZone !.onError.subscribe(
{next: (error: any) => { exceptionHandler.handleError(error); }}));
return _callAndReportToErrorHandler(exceptionHandler, ngZone !, () => {
const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);
initStatus.runInitializers();
return initStatus.donePromise.then(() => {
this._moduleDoBootstrap(moduleRef);
return moduleRef;
});
});
});
}
/**
* Creates an instance of an `@NgModule` for a given platform using the given runtime compiler.
*
* @usageNotes
* ### Simple Example
*
* ```typescript
* @NgModule({
* imports: [BrowserModule]
* })
* class MyModule {}
*
* let moduleRef = platformBrowser().bootstrapModule(MyModule);
* ```
*
*/
bootstrapModule<M>(
moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)|
Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> {
const options = optionsReducer({}, compilerOptions);
return compileNgModuleFactory(this.injector, options, moduleType)
.then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options));
}
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
const appRef = moduleRef.injector.get(ApplicationRef) as ApplicationRef;
if (moduleRef._bootstrapComponents.length > 0) {
moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f));
} else if (moduleRef.instance.ngDoBootstrap) {
moduleRef.instance.ngDoBootstrap(appRef);
} else {
throw new Error(
`The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` +
`Please define one of these.`);
}
this._modules.push(moduleRef);
}
/**
* Register a listener to be called when the platform is disposed.
*/
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
/**
* Retrieve the platform {@link Injector}, which is the parent injector for
* every Angular application on the page and provides singleton providers.
*/
get injector(): Injector { return this._injector; }
/**
* Destroy the Angular platform and all Angular applications on the page.
*/
destroy() {
if (this._destroyed) {
throw new Error('The platform has already been destroyed!');
}
this._modules.slice().forEach(module => module.destroy());
this._destroyListeners.forEach(listener => listener());
this._destroyed = true;
}
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 {
const result = callback();
if (isPromise(result)) {
return result.catch((e: any) => {
ngZone.runOutsideAngular(() => errorHandler.handleError(e));
// rethrow as the exception handler might not do it
throw e;
});
}
return result;
} catch (e) {
ngZone.runOutsideAngular(() => errorHandler.handleError(e));
// rethrow as the exception handler might not do it
throw e;
}
}
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.
*/
@Injectable()
export class ApplicationRef {
/** @internal */
static _tickScope: WtfScopeFn = wtfCreateScope('ApplicationRef#tick()');
feat(core): Add type information to injector.get() (#13785) - Introduce `InjectionToken<T>` which is a parameterized and type-safe version of `OpaqueToken`. DEPRECATION: - `OpaqueToken` is now deprecated, use `InjectionToken<T>` instead. - `Injector.get(token: any, notFoundValue?: any): any` is now deprecated use the same method which is now overloaded as `Injector.get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;`. Migration - Replace `OpaqueToken` with `InjectionToken<?>` and parameterize it. - Migrate your code to only use `Type<?>` or `InjectionToken<?>` as injection tokens. Using other tokens will not be supported in the future. BREAKING CHANGE: - Because `injector.get()` is now parameterize it is possible that code which used to work no longer type checks. Example would be if one injects `Foo` but configures it as `{provide: Foo, useClass: MockFoo}`. The injection instance will be that of `MockFoo` but the type will be `Foo` instead of `any` as in the past. This means that it was possible to call a method on `MockFoo` in the past which now will fail type check. See this example: ``` class Foo {} class MockFoo extends Foo { setupMock(); } var PROVIDERS = [ {provide: Foo, useClass: MockFoo} ]; ... function myTest(injector: Injector) { var foo = injector.get(Foo); // This line used to work since `foo` used to be `any` before this // change, it will now be `Foo`, and `Foo` does not have `setUpMock()`. // The fix is to downcast: `injector.get(Foo) as MockFoo`. foo.setUpMock(); } ``` PR Close #13785
2017-01-03 19:54:46 -05:00
private _bootstrapListeners: ((compRef: ComponentRef<any>) => void)[] = [];
private _views: InternalViewRef[] = [];
private _runningTick: boolean = false;
private _enforceNoNewChanges: boolean = false;
private _stable = true;
/**
* Get a list of component types registered to this application.
* This list is populated even before the component is created.
*/
public readonly componentTypes: Type<any>[] = [];
/**
* Get a list of components registered to this application.
*/
public readonly components: ComponentRef<any>[] = [];
/**
* Returns an Observable that indicates when the application is stable or unstable.
*/
// TODO(issue/24571): remove '!'.
public readonly isStable !: Observable<boolean>;
/** @internal */
constructor(
private _zone: NgZone, private _console: Console, private _injector: Injector,
private _exceptionHandler: ErrorHandler,
private _componentFactoryResolver: ComponentFactoryResolver,
private _initStatus: ApplicationInitStatus) {
this._enforceNoNewChanges = isDevMode();
this._zone.onMicrotaskEmpty.subscribe(
{next: () => { this._zone.run(() => { this.tick(); }); }});
const isCurrentlyStable = new Observable<boolean>((observer: Observer<boolean>) => {
this._stable = this._zone.isStable && !this._zone.hasPendingMacrotasks &&
!this._zone.hasPendingMicrotasks;
this._zone.runOutsideAngular(() => {
observer.next(this._stable);
observer.complete();
});
});
const isStable = new Observable<boolean>((observer: Observer<boolean>) => {
// Create the subscription to onStable outside the Angular Zone so that
// the callback is run outside the Angular Zone.
let stableSub: Subscription;
this._zone.runOutsideAngular(() => {
stableSub = this._zone.onStable.subscribe(() => {
NgZone.assertNotInAngularZone();
// Check whether there are no pending macro/micro tasks in the next tick
// to allow for NgZone to update the state.
scheduleMicroTask(() => {
if (!this._stable && !this._zone.hasPendingMacrotasks &&
!this._zone.hasPendingMicrotasks) {
this._stable = true;
observer.next(true);
}
});
});
});
const unstableSub: Subscription = this._zone.onUnstable.subscribe(() => {
NgZone.assertInAngularZone();
if (this._stable) {
this._stable = false;
this._zone.runOutsideAngular(() => { observer.next(false); });
}
});
return () => {
stableSub.unsubscribe();
unstableSub.unsubscribe();
};
});
(this as{isStable: Observable<boolean>}).isStable =
merge(isCurrentlyStable, isStable.pipe(share()));
}
/**
* Bootstrap a new component at the root level of the application.
*
* @usageNotes
* ### Bootstrap process
*
* When bootstrapping a new root component into an application, Angular mounts the
* specified application component onto DOM elements identified by the componentType's
* selector and kicks off automatic change detection to finish initializing the component.
*
* Optionally, a component can be mounted onto a DOM element that does not match the
* componentType's selector.
*
* ### Example
* {@example core/ts/platform/platform.ts region='longform'}
*/
bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>, rootSelectorOrNode?: string|any):
ComponentRef<C> {
if (!this._initStatus.done) {
throw new Error(
'Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.');
}
let componentFactory: ComponentFactory<C>;
if (componentOrFactory instanceof ComponentFactory) {
componentFactory = componentOrFactory;
} else {
componentFactory =
this._componentFactoryResolver.resolveComponentFactory(componentOrFactory) !;
}
this.componentTypes.push(componentFactory.componentType);
// Create a factory associated with the current module if it's not bound to some other
const ngModule = componentFactory instanceof ComponentFactoryBoundToModule ?
null :
this._injector.get(NgModuleRef);
const selectorOrNode = rootSelectorOrNode || componentFactory.selector;
const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule);
compRef.onDestroy(() => { this._unloadComponent(compRef); });
2016-09-20 17:14:57 -04:00
const testability = compRef.injector.get(Testability, null);
if (testability) {
compRef.injector.get(TestabilityRegistry)
.registerApplication(compRef.location.nativeElement, testability);
}
this._loadComponent(compRef);
if (isDevMode()) {
this._console.log(
`Angular is running in the development mode. Call enableProdMode() to enable the production mode.`);
}
return compRef;
}
/**
* Invoke this method to explicitly process change detection and its side-effects.
*
* In development mode, `tick()` also performs a second change detection cycle to ensure that no
* further changes are detected. If additional changes are picked up during this second cycle,
* bindings in the app have side-effects that cannot be resolved in a single change detection
* pass.
* In this case, Angular throws an error, since an Angular application can only have one change
* detection pass during which all change detection must complete.
*/
tick(): void {
if (this._runningTick) {
throw new Error('ApplicationRef.tick is called recursively');
}
const scope = ApplicationRef._tickScope();
try {
this._runningTick = true;
this._views.forEach((view) => view.detectChanges());
if (this._enforceNoNewChanges) {
this._views.forEach((view) => view.checkNoChanges());
}
} catch (e) {
// Attention: Don't rethrow as it could cancel subscriptions to Observables!
this._zone.runOutsideAngular(() => this._exceptionHandler.handleError(e));
} finally {
this._runningTick = false;
2016-09-20 17:14:57 -04:00
wtfLeave(scope);
}
}
/**
* Attaches a view so that it will be dirty checked.
* The view will be automatically detached when it is destroyed.
* This will throw if the view is already attached to a ViewContainer.
*/
attachView(viewRef: ViewRef): void {
const view = (viewRef as InternalViewRef);
this._views.push(view);
view.attachToAppRef(this);
}
/**
* Detaches a view from dirty checking again.
*/
detachView(viewRef: ViewRef): void {
const view = (viewRef as InternalViewRef);
remove(this._views, view);
view.detachFromAppRef();
}
private _loadComponent(componentRef: ComponentRef<any>): void {
this.attachView(componentRef.hostView);
this.tick();
this.components.push(componentRef);
// Get the listeners lazily to prevent DI cycles.
const listeners =
this._injector.get(APP_BOOTSTRAP_LISTENER, []).concat(this._bootstrapListeners);
listeners.forEach((listener) => listener(componentRef));
}
private _unloadComponent(componentRef: ComponentRef<any>): void {
this.detachView(componentRef.hostView);
remove(this.components, componentRef);
}
/** @internal */
ngOnDestroy() {
// TODO(alxhub): Dispose of the NgZone.
this._views.slice().forEach((view) => view.destroy());
}
/**
* Returns the number of attached views.
*/
get viewCount() { return this._views.length; }
}
function remove<T>(list: T[], el: T): void {
const index = list.indexOf(el);
if (index > -1) {
list.splice(index, 1);
}
}