760 lines
26 KiB
TypeScript
760 lines
26 KiB
TypeScript
/**
|
|
* @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 {getCompilerFacade} from './compiler/compiler_facade';
|
|
import {Console} from './console';
|
|
import {Injectable, InjectionToken, Injector, StaticProvider} from './di';
|
|
import {INJECTOR_SCOPE} from './di/scope';
|
|
import {ErrorHandler} from './error_handler';
|
|
import {DEFAULT_LOCALE_ID} from './i18n/localization';
|
|
import {LOCALE_ID} from './i18n/tokens';
|
|
import {Type} from './interface/type';
|
|
import {ivyEnabled} from './ivy_switch';
|
|
import {COMPILER_OPTIONS, 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 {isComponentResourceResolutionQueueEmpty, resolveComponentResources} from './metadata/resource_loading';
|
|
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
|
|
import {assertNgModuleType} from './render3/assert';
|
|
import {ComponentFactory as R3ComponentFactory} from './render3/component_ref';
|
|
import {setLocaleId} from './render3/i18n';
|
|
import {NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref';
|
|
import {publishDefaultGlobalUtils as _publishDefaultGlobalUtils} from './render3/util/global_utils';
|
|
import {Testability, TestabilityRegistry} from './testability/testability';
|
|
import {isDevMode} from './util/is_dev_mode';
|
|
import {isPromise} from './util/lang';
|
|
import {scheduleMicroTask} from './util/microtask';
|
|
import {stringify} from './util/stringify';
|
|
import {NgZone, NoopNgZone} from './zone/ng_zone';
|
|
|
|
let _platform: PlatformRef;
|
|
|
|
let compileNgModuleFactory:
|
|
<M>(injector: Injector, options: CompilerOptions, moduleType: Type<M>) =>
|
|
Promise<NgModuleFactory<M>> = compileNgModuleFactory__PRE_R3__;
|
|
|
|
function compileNgModuleFactory__PRE_R3__<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_R3__<M>(
|
|
injector: Injector, options: CompilerOptions,
|
|
moduleType: Type<M>): Promise<NgModuleFactory<M>> {
|
|
ngDevMode && assertNgModuleType(moduleType);
|
|
const moduleFactory = new R3NgModuleFactory(moduleType);
|
|
|
|
if (isComponentResourceResolutionQueueEmpty()) {
|
|
return Promise.resolve(moduleFactory);
|
|
}
|
|
|
|
const compilerOptions = injector.get(COMPILER_OPTIONS, []).concat(options);
|
|
const compilerProviders = _mergeArrays(compilerOptions.map(o => o.providers !));
|
|
|
|
// In case there are no compiler providers, we just return the module factory as
|
|
// there won't be any resource loader. This can happen with Ivy, because AOT compiled
|
|
// modules can be still passed through "bootstrapModule". In that case we shouldn't
|
|
// unnecessarily require the JIT compiler.
|
|
if (compilerProviders.length === 0) {
|
|
return Promise.resolve(moduleFactory);
|
|
}
|
|
|
|
const compiler = getCompilerFacade();
|
|
const compilerInjector = Injector.create({providers: compilerProviders});
|
|
const resourceLoader = compilerInjector.get(compiler.ResourceLoader);
|
|
// The resource loader can also return a string while the "resolveComponentResources"
|
|
// always expects a promise. Therefore we need to wrap the returned value in a promise.
|
|
return resolveComponentResources(url => Promise.resolve(resourceLoader.get(url)))
|
|
.then(() => moduleFactory);
|
|
}
|
|
|
|
// the `window.ng` global utilities are only available in non-VE versions of
|
|
// Angular. The function switch below will make sure that the code is not
|
|
// included into Angular when PRE mode is active.
|
|
export function publishDefaultGlobalUtils__PRE_R3__() {}
|
|
export function publishDefaultGlobalUtils__POST_R3__() {
|
|
ngDevMode && _publishDefaultGlobalUtils();
|
|
}
|
|
|
|
let publishDefaultGlobalUtils: () => any = publishDefaultGlobalUtils__PRE_R3__;
|
|
|
|
let isBoundToModule: <C>(cf: ComponentFactory<C>) => boolean = isBoundToModule__PRE_R3__;
|
|
|
|
export function isBoundToModule__PRE_R3__<C>(cf: ComponentFactory<C>): boolean {
|
|
return cf instanceof ComponentFactoryBoundToModule;
|
|
}
|
|
|
|
export function isBoundToModule__POST_R3__<C>(cf: ComponentFactory<C>): boolean {
|
|
return (cf as R3ComponentFactory<C>).isBoundToModule;
|
|
}
|
|
|
|
export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken<boolean>('AllowMultipleToken');
|
|
|
|
|
|
|
|
/**
|
|
* A token for third-party components that can register themselves with NgProbe.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export class NgProbeToken {
|
|
constructor(public name: string, public token: any) {}
|
|
}
|
|
|
|
/**
|
|
* Creates a platform.
|
|
* Platforms have to be eagerly created via this function.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
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.');
|
|
}
|
|
publishDefaultGlobalUtils();
|
|
_platform = injector.get(PlatformRef);
|
|
const inits = injector.get(PLATFORM_INITIALIZER, null);
|
|
if (inits) inits.forEach((init: any) => init());
|
|
return _platform;
|
|
}
|
|
|
|
/**
|
|
* Creates a factory for a platform
|
|
*
|
|
* @publicApi
|
|
*/
|
|
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}, {
|
|
provide: INJECTOR_SCOPE,
|
|
useValue: 'platform'
|
|
});
|
|
createPlatform(Injector.create({providers: injectedProviders, name: desc}));
|
|
}
|
|
}
|
|
return assertPlatform(marker);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Checks that there currently is a platform which contains the given token as a provider.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export function assertPlatform(requiredToken: any): PlatformRef {
|
|
const platform = getPlatform();
|
|
|
|
if (!platform) {
|
|
throw new Error('No platform exists!');
|
|
}
|
|
|
|
if (!platform.injector.get(requiredToken, null)) {
|
|
throw new Error(
|
|
'A platform with a different configuration has been created. Please destroy it first.');
|
|
}
|
|
|
|
return platform;
|
|
}
|
|
|
|
/**
|
|
* Destroy the existing platform.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export function destroyPlatform(): void {
|
|
if (_platform && !_platform.destroyed) {
|
|
_platform.destroy();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the current platform.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export function getPlatform(): PlatformRef|null {
|
|
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';
|
|
|
|
/**
|
|
* Optionally specify coalescing event change detections or not.
|
|
* Consider the following case.
|
|
*
|
|
* <div (click)="doSomething()">
|
|
* <button (click)="doSomethingElse()"></button>
|
|
* </div>
|
|
*
|
|
* When button is clicked, because of the event bubbling, both
|
|
* event handlers will be called and 2 change detections will be
|
|
* triggered. We can colesce such kind of events to only trigger
|
|
* change detection only once.
|
|
*
|
|
* By default, this option will be false. So the events will not be
|
|
* coalesced and the change detection will be triggered multiple times.
|
|
* And if this option be set to true, the change detection will be
|
|
* triggered async by scheduling a animation frame. So in the case above,
|
|
* the change detection will only be trigged once.
|
|
*/
|
|
ngZoneEventCoalescing?: boolean;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
@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);
|
|
* ```
|
|
*/
|
|
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 ngZoneEventCoalescing = (options && options.ngZoneEventCoalescing) || false;
|
|
const ngZone = getNgZone(ngZoneOption, ngZoneEventCoalescing);
|
|
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|null = moduleRef.injector.get(ErrorHandler, null);
|
|
if (!exceptionHandler) {
|
|
throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
|
|
}
|
|
// If the `LOCALE_ID` provider is defined at bootstrap we set the value for runtime i18n (ivy)
|
|
if (ivyEnabled) {
|
|
const localeId = moduleRef.injector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
|
|
setLocaleId(localeId || DEFAULT_LOCALE_ID);
|
|
}
|
|
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' | undefined, ngZoneEventCoalescing: boolean): NgZone {
|
|
let ngZone: NgZone;
|
|
|
|
if (ngZoneOption === 'noop') {
|
|
ngZone = new NoopNgZone();
|
|
} else {
|
|
ngZone = (ngZoneOption === 'zone.js' ? undefined : ngZoneOption) || new NgZone({
|
|
enableLongStackTrace: isDevMode(),
|
|
shouldCoalesceEventChangeDetection: ngZoneEventCoalescing
|
|
});
|
|
}
|
|
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.
|
|
*
|
|
* @usageNotes
|
|
*
|
|
* {@a is-stable-examples}
|
|
* ### isStable examples and caveats
|
|
*
|
|
* Note two important points about `isStable`, demonstrated in the examples below:
|
|
* - the application will never be stable if you start any kind
|
|
* of recurrent asynchronous task when the application starts
|
|
* (for example for a polling process, started with a `setInterval`, a `setTimeout`
|
|
* or using RxJS operators like `interval`);
|
|
* - the `isStable` Observable runs outside of the Angular zone.
|
|
*
|
|
* Let's imagine that you start a recurrent task
|
|
* (here incrementing a counter, using RxJS `interval`),
|
|
* and at the same time subscribe to `isStable`.
|
|
*
|
|
* ```
|
|
* constructor(appRef: ApplicationRef) {
|
|
* appRef.isStable.pipe(
|
|
* filter(stable => stable)
|
|
* ).subscribe(() => console.log('App is stable now');
|
|
* interval(1000).subscribe(counter => console.log(counter));
|
|
* }
|
|
* ```
|
|
* In this example, `isStable` will never emit `true`,
|
|
* and the trace "App is stable now" will never get logged.
|
|
*
|
|
* If you want to execute something when the app is stable,
|
|
* you have to wait for the application to be stable
|
|
* before starting your polling process.
|
|
*
|
|
* ```
|
|
* constructor(appRef: ApplicationRef) {
|
|
* appRef.isStable.pipe(
|
|
* first(stable => stable),
|
|
* tap(stable => console.log('App is stable now')),
|
|
* switchMap(() => interval(1000))
|
|
* ).subscribe(counter => console.log(counter));
|
|
* }
|
|
* ```
|
|
* In this example, the trace "App is stable now" will be logged
|
|
* and then the counter starts incrementing every second.
|
|
*
|
|
* Note also that this Observable runs outside of the Angular zone,
|
|
* which means that the code in the subscription
|
|
* to this Observable will not trigger the change detection.
|
|
*
|
|
* Let's imagine that instead of logging the counter value,
|
|
* you update a field of your component
|
|
* and display it in its template.
|
|
*
|
|
* ```
|
|
* constructor(appRef: ApplicationRef) {
|
|
* appRef.isStable.pipe(
|
|
* first(stable => stable),
|
|
* switchMap(() => interval(1000))
|
|
* ).subscribe(counter => this.value = counter);
|
|
* }
|
|
* ```
|
|
* As the `isStable` Observable runs outside the zone,
|
|
* the `value` field will be updated properly,
|
|
* but the template will not be refreshed!
|
|
*
|
|
* You'll have to manually trigger the change detection to update the template.
|
|
*
|
|
* ```
|
|
* constructor(appRef: ApplicationRef, cd: ChangeDetectorRef) {
|
|
* appRef.isStable.pipe(
|
|
* first(stable => stable),
|
|
* switchMap(() => interval(1000))
|
|
* ).subscribe(counter => {
|
|
* this.value = counter;
|
|
* cd.detectChanges();
|
|
* });
|
|
* }
|
|
* ```
|
|
*
|
|
* Or make the subscription callback run inside the zone.
|
|
*
|
|
* ```
|
|
* constructor(appRef: ApplicationRef, zone: NgZone) {
|
|
* appRef.isStable.pipe(
|
|
* first(stable => stable),
|
|
* switchMap(() => interval(1000))
|
|
* ).subscribe(counter => zone.run(() => this.value = counter));
|
|
* }
|
|
* ```
|
|
*
|
|
* @publicApi
|
|
*/
|
|
@Injectable()
|
|
export class ApplicationRef {
|
|
/** @internal */
|
|
static _tickScope: WtfScopeFn = wtfCreateScope('ApplicationRef#tick()');
|
|
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.
|
|
*
|
|
* @see [Usage notes](#is-stable-examples) for examples and caveats when using this API.
|
|
*/
|
|
// 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 =
|
|
isBoundToModule(componentFactory) ? undefined : this._injector.get(NgModuleRef);
|
|
const selectorOrNode = rootSelectorOrNode || componentFactory.selector;
|
|
const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule);
|
|
|
|
compRef.onDestroy(() => { this._unloadComponent(compRef); });
|
|
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;
|
|
for (let view of this._views) {
|
|
view.detectChanges();
|
|
}
|
|
if (this._enforceNoNewChanges) {
|
|
for (let view of this._views) {
|
|
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;
|
|
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);
|
|
}
|
|
}
|
|
|
|
function _mergeArrays(parts: any[][]): any[] {
|
|
const result: any[] = [];
|
|
parts.forEach((part) => part && result.push(...part));
|
|
return result;
|
|
}
|