Plural ICU expressions depend on the locale (different languages have different plural forms). Until now the locale was hard coded as `en-US`. For compatibility reasons, if you use ivy with AOT and bootstrap your app with `bootstrapModule` then the `LOCALE_ID` token will be set automatically for ivy, which is then used to get the correct plural form. If you use JIT, you need to define the `LOCALE_ID` provider on the module that you bootstrap. For `TestBed` you can use either `configureTestingModule` or `overrideProvider` to define that provider. If you don't use the compat mode and start your app with `renderComponent` you need to call `ɵsetLocaleId` manually to define the `LOCALE_ID` before bootstrap. We expect this to change once we start adding the new i18n APIs, so don't rely on this function (there's a reason why it's a private export). PR Close #29249
		
			
				
	
	
		
			714 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			714 lines
		
	
	
		
			24 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 {ErrorHandler} from './error_handler';
 | |
| import {LOCALE_ID} from './i18n/tokens';
 | |
| import {Type} from './interface/type';
 | |
| 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 {DEFAULT_LOCALE_ID, setLocaleId} from './render3/i18n';
 | |
| import {NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref';
 | |
| 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);
 | |
| }
 | |
| 
 | |
| 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.');
 | |
|   }
 | |
|   _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});
 | |
|         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';
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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 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?');
 | |
|       }
 | |
|       // If the `LOCALE_ID` provider is defined at bootstrap we set the value for runtime i18n (ivy)
 | |
|       const localeId = moduleRef.injector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
 | |
|       setLocaleId(localeId);
 | |
|       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.
 | |
|  *
 | |
|  * @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) ? null : 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;
 | |
| }
 |