/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/core/src/application/application_ref.ts
899 строк
31 KB
Matthieu Riegler
refactor(core): use the `@Service` decorator where possible.
08 май 2026, 02:03
08 май 2026, 02:03
a7dab60
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC 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.dev/license */ import '../util/ng_hmr_mode'; import '../util/ng_jit_mode'; import '../util/ng_server_mode'; import {type Observable, Subject, type Subscription} from 'rxjs'; import {map} from 'rxjs/operators'; import { getActiveConsumer, setActiveConsumer, setThrowInvalidWriteToSignalError, } from '../../primitives/signals'; import {ProfilerEvent} from '../../primitives/devtools'; import {ZONELESS_ENABLED} from '../change_detection/scheduling/zoneless_scheduling'; import {Console} from '../console'; import {inject, Service} from '../di'; import {InjectionToken} from '../di/injection_token'; import {Injector} from '../di/injector'; import {EnvironmentInjector, type R3Injector} from '../di/r3_injector'; import {INTERNAL_APPLICATION_ERROR_HANDLER} from '../error_handler'; import {formatRuntimeError, RuntimeError, RuntimeErrorCode} from '../errors'; import {Type} from '../interface/type'; import {ComponentRef} from '../linker/component_factory'; import {NgModuleRef} from '../linker/ng_module_factory'; import {ViewRef} from '../linker/view_ref'; import {PendingTasksInternal} from '../pending_tasks_internal'; import {RendererFactory2} from '../render/api'; import {AfterRenderManager} from '../render3/after_render/manager'; import {ComponentFactory} from '../render3/component_ref'; import {getComponentDef, isStandalone} from '../render3/def_getters'; import type {Binding, DirectiveWithBindings} from '../render3/dynamic_bindings'; import {ChangeDetectionMode, detectChangesInternal} from '../render3/instructions/change_detection'; import {profiler} from '../render3/profiler'; import {isReactiveLViewConsumer} from '../render3/reactive_lview_consumer'; import {EffectScheduler} from '../render3/reactivity/root_effect_scheduler'; import {publishDefaultGlobalUtils as _publishDefaultGlobalUtils} from '../render3/util/global_utils'; import {requiresRefreshOrTraversal} from '../render3/util/view_utils'; import {ViewRef as InternalViewRef} from '../render3/view_ref'; import {TESTABILITY} from '../testability/testability'; import {NgZone} from '../zone/ng_zone'; import {ApplicationInitStatus} from './application_init'; import {TracingAction, TracingService, TracingSnapshot} from './tracing'; /** * A DI token that provides a set of callbacks to * be called for every component that is bootstrapped. * * Each callback must take a `ComponentRef` instance and return nothing. * * `(componentRef: ComponentRef) => void` * * @publicApi */ export const APP_BOOTSTRAP_LISTENER = new InjectionToken< ReadonlyArray<(compRef: ComponentRef<any>) => void> >(ngDevMode ? 'appBootstrapListener' : ''); export function publishDefaultGlobalUtils() { ngDevMode && _publishDefaultGlobalUtils(); } /** * Sets the error for an invalid write to a signal to be an Angular `RuntimeError`. */ export function publishSignalConfiguration(): void { setThrowInvalidWriteToSignalError(() => { let errorMessage = ''; if (ngDevMode) { const activeConsumer = getActiveConsumer(); errorMessage = activeConsumer && isReactiveLViewConsumer(activeConsumer) ? 'Writing to signals is not allowed while Angular renders the template (eg. interpolations)' : 'Writing to signals is not allowed in a `computed`'; } throw new RuntimeError(RuntimeErrorCode.SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT, errorMessage); }); } /** * Provides additional options to the bootstrapping process. * * @publicApi * @deprecated 20.2 Configure `NgZone` in the `providers` array of the application module instead. */ export interface BootstrapOptions { /** * Optionally specify which `NgZone` should be used when not configured in the providers. * * - Provide your own `NgZone` instance. * - `zone.js` - Use default `NgZone` which requires `Zone.js`. * - `noop` - Use `NoopNgZone` which does nothing. * * @deprecated BootstrapOptions is deprecated. Provide `NgZone` in the `providers` array of the module instead. */ ngZone?: NgZone | 'zone.js' | 'noop'; /** * Optionally specify coalescing event change detections or not. * Consider the following case. * * ```html * <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 coalesce 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 triggered once. * * @deprecated BootstrapOptions is deprecated. Use `provideZoneChangeDetection` instead to configure coalescing. */ ngZoneEventCoalescing?: boolean; /** * Optionally specify if `NgZone#run()` method invocations should be coalesced * into a single change detection. * * Consider the following case. * ```ts * for (let i = 0; i < 10; i ++) { * ngZone.run(() => { * // do something * }); * } * ``` * * This case triggers the change detection multiple times. * With ngZoneRunCoalescing options, all change detections in an event loop trigger only once. * In addition, the change detection executes in requestAnimation. * * @deprecated BootstrapOptions is deprecated. Use `provideZoneChangeDetection` instead to configure coalescing. */ ngZoneRunCoalescing?: boolean; } /** Maximum number of times ApplicationRef will refresh all attached views in a single tick. */ const MAXIMUM_REFRESH_RERUNS = 10; export function optionsReducer<T extends Object>(dst: T, objs: T | T[]): T { if (Array.isArray(objs)) { return objs.reduce(optionsReducer, dst); } return {...dst, ...objs}; } /** * A reference to an Angular application running on a page. * * @usageNotes * ### 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`. * * ```ts * 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. * * ```ts * 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. * * ```ts * 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. * * ```ts * 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. * * ```ts * constructor(appRef: ApplicationRef, zone: NgZone) { * appRef.isStable.pipe( * first(stable => stable), * switchMap(() => interval(1000)) * ).subscribe(counter => zone.run(() => this.value = counter)); * } * ``` * * @publicApi */ @Service() export class ApplicationRef { /** @internal */ _runningTick: boolean = false; private _destroyed = false; private _destroyListeners: Array<() => void> = []; /** @internal */ _views: InternalViewRef<unknown>[] = []; private readonly internalErrorHandler = inject(INTERNAL_APPLICATION_ERROR_HANDLER); private readonly afterRenderManager = inject(AfterRenderManager); private readonly zonelessEnabled = inject(ZONELESS_ENABLED); private readonly rootEffectScheduler = inject(EffectScheduler); /** * Current dirty state of the application across a number of dimensions (views, afterRender hooks, * etc). * * A flag set here means that `tick()` will attempt to resolve the dirtiness when executed. * * @internal */ dirtyFlags = ApplicationRefDirtyFlags.None; /** * Most recent snapshot from the `TracingService`, if any. * * This snapshot attempts to capture the context when `tick()` was first * scheduled. It then runs wrapped in this context. * * @internal */ tracingSnapshot: TracingSnapshot | null = null; // Needed for ComponentFixture temporarily during migration of autoDetect behavior // Eventually the hostView of the fixture should just attach to ApplicationRef. private allTestViews: Set<InternalViewRef<unknown>> = new Set(); private autoDetectTestViews: Set<InternalViewRef<unknown>> = new Set(); /** @internal */ includeAllTestViews = false; /** @internal */ afterTick = new Subject<void>(); /** @internal */ get allViews(): Array<InternalViewRef<unknown>> { return [ ...(this.includeAllTestViews ? this.allTestViews : this.autoDetectTestViews).keys(), ...this._views, ]; } /** * Indicates whether this instance was destroyed. */ get destroyed() { return this._destroyed; } /** * 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>[] = []; private internalPendingTask = inject(PendingTasksInternal); /** * Returns an Observable that indicates when the application is stable or unstable. */ public get isStable(): Observable<boolean> { // This is a getter because it might be invoked after the application has been destroyed. return this.internalPendingTask.hasPendingTasksObservable.pipe(map((pending) => !pending)); } constructor() { // Inject the tracing service to initialize it. inject(TracingService, {optional: true}); } /** * @returns A promise that resolves when the application becomes stable */ whenStable(): Promise<void> { let subscription: Subscription; return new Promise<void>((resolve) => { subscription = this.isStable.subscribe({ next: (stable) => { if (stable) { resolve(); } }, }); }).finally(() => { subscription.unsubscribe(); }); } private readonly _injector = inject(EnvironmentInjector); private _rendererFactory: RendererFactory2 | null = null; /** * The `EnvironmentInjector` used to create this application. */ get injector(): EnvironmentInjector { return this._injector; } /** * Bootstrap a component onto the element identified by its selector or, optionally, to a * specified element. * * @usageNotes * ### Bootstrap process * * When bootstrapping a component, Angular mounts it onto a target DOM element * and kicks off automatic change detection. The target DOM element can be * provided using the `hostElement` argument. * * If the target DOM element is not provided, Angular tries to find one on a page * using the `selector` of the component that is being bootstrapped * (first matched element is used). * * ### Example * * Generally, we define the component to bootstrap in the `bootstrap` array of `NgModule`, * but it requires us to know the component while writing the application code. * * Imagine a situation where we have to wait for an API call to decide about the component to * bootstrap. We can use the `ngDoBootstrap` hook of the `NgModule` and call this method to * dynamically bootstrap a component. * * {@example core/ts/platform/platform.ts region='componentSelector'} * * Optionally, a component can be mounted onto a DOM element that does not match the * selector of the bootstrapped component. * * In the following example, we are providing a CSS selector to match the target element. * * {@example core/ts/platform/platform.ts region='cssSelector'} * * While in this example, we are providing reference to a DOM node. * * {@example core/ts/platform/platform.ts region='domNode'} */ bootstrap<C>( component: Type<C>, options?: { hostElement?: Element | string; directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[]; bindings?: Binding[]; }, ): ComponentRef<C>; bootstrap<C>(component: Type<C>, hostElement?: Element | string): ComponentRef<C>; /** * Bootstrap a component onto the element identified by its selector or, optionally, to a * specified element. * * @usageNotes * ### Bootstrap process * * When bootstrapping a component, Angular mounts it onto a target DOM element * and kicks off automatic change detection. The target DOM element can be * provided using the `hostElement` argument. * * If the target DOM element is not provided, Angular tries to find one on a page * using the `selector` of the component that is being bootstrapped * (first matched element is used). * * ### Example * * Generally, we define the component to bootstrap in the `bootstrap` array of `NgModule`, * but it requires us to know the component while writing the application code. * * Imagine a situation where we have to wait for an API call to decide about the component to * bootstrap. We can use the `ngDoBootstrap` hook of the `NgModule` and call this method to * dynamically bootstrap a component. * * {@example core/ts/platform/platform.ts region='componentSelector'} * * Optionally, a component can be mounted onto a DOM element that does not match the * selector of the bootstrapped component. * * In the following example, we are providing a CSS selector to match the target element. * * {@example core/ts/platform/platform.ts region='cssSelector'} * * While in this example, we are providing reference to a DOM node. * * {@example core/ts/platform/platform.ts region='domNode'} */ bootstrap<C>(component: Type<C>, rootSelectorOrNode?: string | any): ComponentRef<C> { return this.bootstrapImpl(component, rootSelectorOrNode); } private bootstrapImpl<C>( component: Type<C>, hostElementOrOptions?: | Element | string | undefined | { hostElement?: Element | string | undefined; directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[]; bindings?: Binding[]; }, injector: Injector = Injector.NULL, ): ComponentRef<C> { const ngZone = this._injector.get(NgZone); return ngZone.run(() => { profiler(ProfilerEvent.BootstrapComponentStart); (typeof ngDevMode === 'undefined' || ngDevMode) && warnIfDestroyed(this._destroyed); const initStatus = this._injector.get(ApplicationInitStatus); if (!initStatus.done) { let errorMessage = ''; if (typeof ngDevMode === 'undefined' || ngDevMode) { const standalone = isStandalone(component); errorMessage = 'Cannot bootstrap as there are still asynchronous initializers running.' + (standalone ? '' : ' Bootstrap components in the `ngDoBootstrap` method of the root module.'); } throw new RuntimeError(RuntimeErrorCode.ASYNC_INITIALIZERS_STILL_RUNNING, errorMessage); } const componentDef = getComponentDef(component)!; const ngModule = this._injector.get(NgModuleRef); const componentFactory = new ComponentFactory<C>(componentDef, ngModule); this.componentTypes.push(component); // Create a factory associated with the current module if it's not bound to some other const {hostElement, directives, bindings} = normalizeBootstrapOptions(hostElementOrOptions); const selectorOrNode = hostElement || componentFactory.selector; const compRef = componentFactory.create( injector, [], selectorOrNode, ngModule.injector, directives, bindings, ); const nativeElement = compRef.location.nativeElement; const testability = compRef.injector.get(TESTABILITY, null); testability?.registerApplication(nativeElement); compRef.onDestroy(() => { this.detachView(compRef.hostView); remove(this.components, compRef); testability?.unregisterApplication(nativeElement); }); this._loadComponent(compRef); if (typeof ngDevMode === 'undefined' || ngDevMode) { const _console = this._injector.get(Console); _console.log(`Angular is running in development mode.`); } profiler(ProfilerEvent.BootstrapComponentEnd, compRef); 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.zonelessEnabled) { this.dirtyFlags |= ApplicationRefDirtyFlags.ViewTreeGlobal; } this._tick(); } /** @internal */ _tick(): void { profiler(ProfilerEvent.ChangeDetectionStart); if (this.tracingSnapshot !== null) { // Ensure we always run `tickImpl()` in the context of the most recent snapshot, // if one exists. Snapshots may be reference counted by the implementation so // we want to ensure that if we request a snapshot that we use it. this.tracingSnapshot.run(TracingAction.CHANGE_DETECTION, this.tickImpl); } else { this.tickImpl(); } } private tickImpl = (): void => { (typeof ngDevMode === 'undefined' || ngDevMode) && warnIfDestroyed(this._destroyed); if (this._runningTick) { profiler(ProfilerEvent.ChangeDetectionEnd); throw new RuntimeError( RuntimeErrorCode.RECURSIVE_APPLICATION_REF_TICK, ngDevMode && 'ApplicationRef.tick is called recursively', ); } const prevConsumer = setActiveConsumer(null); try { this._runningTick = true; this.synchronize(); if (typeof ngDevMode === 'undefined' || ngDevMode) { for (let view of this.allViews) { view.checkNoChanges(); } } } finally { this._runningTick = false; this.tracingSnapshot?.dispose(); this.tracingSnapshot = null; setActiveConsumer(prevConsumer); this.afterTick.next(); profiler(ProfilerEvent.ChangeDetectionEnd); } }; /** * Performs the core work of synchronizing the application state with the UI, resolving any * pending dirtiness (potentially in a loop). */ private synchronize(): void { if (this._rendererFactory === null && !(this._injector as R3Injector).destroyed) { this._rendererFactory = this._injector.get(RendererFactory2, null, {optional: true}); } let runs = 0; while (this.dirtyFlags !== ApplicationRefDirtyFlags.None && runs++ < MAXIMUM_REFRESH_RERUNS) { profiler(ProfilerEvent.ChangeDetectionSyncStart); try { this.synchronizeOnce(); } finally { profiler(ProfilerEvent.ChangeDetectionSyncEnd); } } if ((typeof ngDevMode === 'undefined' || ngDevMode) && runs >= MAXIMUM_REFRESH_RERUNS) { throw new RuntimeError( RuntimeErrorCode.INFINITE_CHANGE_DETECTION, ngDevMode && 'Infinite change detection while refreshing application views. ' + 'Ensure views are not calling `markForCheck` on every template execution or ' + 'that afterRender hooks always mark views for check.', ); } } /** * Perform a single synchronization pass. */ private synchronizeOnce(): void { // First, process any dirty root effects. if (this.dirtyFlags & ApplicationRefDirtyFlags.RootEffects) { this.dirtyFlags &= ~ApplicationRefDirtyFlags.RootEffects; this.rootEffectScheduler.flush(); } // First check dirty views, if there are any. let ranDetectChanges = false; if (this.dirtyFlags & ApplicationRefDirtyFlags.ViewTreeAny) { // Change detection on views starts in targeted mode (only check components if they're // marked as dirty) unless global checking is specifically requested via APIs like // `ApplicationRef.tick()` and the `NgZone` integration. const useGlobalCheck = Boolean(this.dirtyFlags & ApplicationRefDirtyFlags.ViewTreeGlobal); // Clear the view-related dirty flags. this.dirtyFlags &= ~ApplicationRefDirtyFlags.ViewTreeAny; // Set the AfterRender bit, as we're checking views and will need to run afterRender hooks. this.dirtyFlags |= ApplicationRefDirtyFlags.AfterRender; // Check all potentially dirty views. for (let {_lView} of this.allViews) { // When re-checking, only check views which actually need it. if (!useGlobalCheck && !requiresRefreshOrTraversal(_lView)) { continue; } const mode = useGlobalCheck && !this.zonelessEnabled ? // Global mode includes `CheckAlways` views. // When using zoneless, all root views must be explicitly marked for refresh, even if they are // `CheckAlways`. ChangeDetectionMode.Global : // Only refresh views with the `RefreshView` flag or views is a changed signal ChangeDetectionMode.Targeted; detectChangesInternal(_lView, mode); ranDetectChanges = true; } // If `markForCheck()` was called during view checking, it will have set the `ViewTreeCheck` // flag. We clear the flag here because, for backwards compatibility, `markForCheck()` // during view checking doesn't cause the view to be re-checked. this.dirtyFlags &= ~ApplicationRefDirtyFlags.ViewTreeCheck; // Check if any views are still dirty after checking and we need to loop back. this.syncDirtyFlagsWithViews(); if ( this.dirtyFlags & (ApplicationRefDirtyFlags.ViewTreeAny | ApplicationRefDirtyFlags.RootEffects) ) { // If any views or effects are still dirty after checking, loop back before running render // hooks. return; } } if (!ranDetectChanges) { // If we skipped refreshing views above, there might still be unflushed animations // because we never called `detectChangesInternal` on the views. this._rendererFactory?.begin?.(); this._rendererFactory?.end?.(); } // Even if there were no dirty views, afterRender hooks might still be dirty. if (this.dirtyFlags & ApplicationRefDirtyFlags.AfterRender) { this.dirtyFlags &= ~ApplicationRefDirtyFlags.AfterRender; this.afterRenderManager.execute(); // afterRender hooks might influence dirty flags. } this.syncDirtyFlagsWithViews(); } /** * Checks `allViews` for views which require refresh/traversal, and updates `dirtyFlags` * accordingly, with two potential behaviors: * * 1. If any of our views require updating, then this adds the `ViewTreeTraversal` dirty flag. * This _should_ be a no-op, since the scheduler should've added the flag at the same time the * view was marked as needing updating. * * TODO(alxhub): figure out if this behavior is still needed for edge cases. * * 2. If none of our views require updating, then clear the view-related `dirtyFlag`s. This * happens when the scheduler is notified of a view becoming dirty, but the view itself isn't * reachable through traversal from our roots (e.g. it's detached from the CD tree). */ private syncDirtyFlagsWithViews(): void { if (this.allViews.some(({_lView}) => requiresRefreshOrTraversal(_lView))) { // If after running all afterRender callbacks new views are dirty, ensure we loop back. this.dirtyFlags |= ApplicationRefDirtyFlags.ViewTreeTraversal; return; } else { // Even though this flag may be set, none of _our_ views require traversal, and so the // `ApplicationRef` doesn't require any repeated checking. this.dirtyFlags &= ~ApplicationRefDirtyFlags.ViewTreeAny; } } /** * 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 { (typeof ngDevMode === 'undefined' || ngDevMode) && warnIfDestroyed(this._destroyed); const view = viewRef as InternalViewRef<unknown>; this._views.push(view); view.attachToAppRef(this); } /** * Detaches a view from dirty checking again. */ detachView(viewRef: ViewRef): void { (typeof ngDevMode === 'undefined' || ngDevMode) && warnIfDestroyed(this._destroyed); const view = viewRef as InternalViewRef<unknown>; remove(this._views, view); view.detachFromAppRef(); } private _loadComponent(componentRef: ComponentRef<any>): void { this.attachView(componentRef.hostView); try { this.tick(); } catch (e) { this.internalErrorHandler(e); } this.components.push(componentRef); // Get the listeners lazily to prevent DI cycles. const listeners = this._injector.get(APP_BOOTSTRAP_LISTENER, []); if (ngDevMode && !Array.isArray(listeners)) { throw new RuntimeError( RuntimeErrorCode.INVALID_MULTI_PROVIDER, 'Unexpected type of the `APP_BOOTSTRAP_LISTENER` token value ' + `(expected an array, but got ${typeof listeners}). ` + 'Please check that the `APP_BOOTSTRAP_LISTENER` token is configured as a ' + '`multi: true` provider.', ); } listeners.forEach((listener) => listener(componentRef)); } /** @internal */ ngOnDestroy() { if (this._destroyed) return; try { // Call all the lifecycle hooks. this._destroyListeners.forEach((listener) => listener()); // Destroy all registered views. this._views.slice().forEach((view) => view.destroy()); } finally { // Indicate that this instance is destroyed. this._destroyed = true; // Release all references. this._views = []; this._destroyListeners = []; } } /** * Registers a listener to be called when an instance is destroyed. * * @param callback A callback function to add as a listener. * @returns A function which unregisters a listener. */ onDestroy(callback: () => void): VoidFunction { (typeof ngDevMode === 'undefined' || ngDevMode) && warnIfDestroyed(this._destroyed); this._destroyListeners.push(callback); return () => remove(this._destroyListeners, callback); } /** * Destroys an Angular application represented by this `ApplicationRef`. Calling this function * will destroy the associated environment injectors as well as all the bootstrapped components * with their views. */ destroy(): void { if (this._destroyed) { throw new RuntimeError( RuntimeErrorCode.APPLICATION_REF_ALREADY_DESTROYED, ngDevMode && 'This instance of the `ApplicationRef` has already been destroyed.', ); } const injector = this._injector as R3Injector; // Check that this injector instance supports destroy operation. if (injector.destroy && !injector.destroyed) { // Destroying an underlying injector will trigger the `ngOnDestroy` lifecycle // hook, which invokes the remaining cleanup actions. injector.destroy(); } } /** * Returns the number of attached views. */ get viewCount() { return this._views.length; } } function normalizeBootstrapOptions( hostElementOrOptions: | Element | string | undefined | { hostElement?: Element | string | undefined; directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[]; bindings?: Binding[]; }, ): { hostElement?: Element | string | undefined; directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[]; bindings?: Binding[]; } { if ( hostElementOrOptions === undefined || typeof hostElementOrOptions === 'string' || hostElementOrOptions instanceof Element ) { return {hostElement: hostElementOrOptions}; } return hostElementOrOptions; } function warnIfDestroyed(destroyed: boolean): void { if (destroyed) { console.warn( formatRuntimeError( RuntimeErrorCode.APPLICATION_REF_ALREADY_DESTROYED, 'This instance of the `ApplicationRef` has already been destroyed.', ), ); } } export function remove<T>(list: T[], el: T): void { const index = list.indexOf(el); if (index > -1) { list.splice(index, 1); } } export const enum ApplicationRefDirtyFlags { None = 0, /** * A global change detection round has been requested. */ ViewTreeGlobal = 0b00000001, /** * Part of the view tree is marked for traversal. */ ViewTreeTraversal = 0b00000010, /** * Part of the view tree is marked to be checked (dirty). */ ViewTreeCheck = 0b00000100, /** * Helper for any view tree bit being set. */ ViewTreeAny = ViewTreeGlobal | ViewTreeTraversal | ViewTreeCheck, /** * After render hooks need to run. */ AfterRender = 0b00001000, /** * Effects at the `ApplicationRef` level. */ RootEffects = 0b00010000, }