/**
* @license Angular v20.0.0
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
import { getCurrentInjector, isNotFound, setCurrentInjector } from './primitives/di.mjs';
import { getActiveConsumer, SIGNAL, createSignal, signalSetFn, signalUpdateFn } from './signal-ePSl6jXn.mjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { NotFoundError, isNotFound as isNotFound$1 } from '@angular/core/primitives/di';
import { setActiveConsumer } from '@angular/core/primitives/signals';
/**
* Base URL for the error details page.
*
* Keep this constant in sync across:
* - packages/compiler-cli/src/ngtsc/diagnostics/src/error_details_base_url.ts
* - packages/core/src/error_details_base_url.ts
*/
const ERROR_DETAILS_PAGE_BASE_URL = 'https://angular.dev/errors';
/**
* URL for the XSS security documentation.
*/
const XSS_SECURITY_URL = 'https://angular.dev/best-practices/security#preventing-cross-site-scripting-xss';
/**
* Class that represents a runtime error.
* Formats and outputs the error message in a consistent way.
*
* Example:
* ```ts
* throw new RuntimeError(
* RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED,
* ngDevMode && 'Injector has already been destroyed.');
* ```
*
* Note: the `message` argument contains a descriptive error message as a string in development
* mode (when the `ngDevMode` is defined). In production mode (after tree-shaking pass), the
* `message` argument becomes `false`, thus we account for it in the typings and the runtime
* logic.
*/
class RuntimeError extends Error {
code;
constructor(code, message) {
super(formatRuntimeError(code, message));
this.code = code;
}
}
function formatRuntimeErrorCode(code) {
// Error code might be a negative number, which is a special marker that instructs the logic to
// generate a link to the error details page on angular.io.
// We also prepend `0` to non-compile-time errors.
return `NG0${Math.abs(code)}`;
}
/**
* Called to format a runtime error.
* See additional info on the `message` argument type in the `RuntimeError` class description.
*/
function formatRuntimeError(code, message) {
const fullCode = formatRuntimeErrorCode(code);
let errorMessage = `${fullCode}${message ? ': ' + message : ''}`;
if (ngDevMode && code < 0) {
const addPeriodSeparator = !errorMessage.match(/[.,;!?\n]$/);
const separator = addPeriodSeparator ? '.' : '';
errorMessage = `${errorMessage}${separator} Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/${fullCode}`;
}
return errorMessage;
}
const _global = globalThis;
function ngDevModeResetPerfCounters() {
const locationString = typeof location !== 'undefined' ? location.toString() : '';
const newCounters = {
hydratedNodes: 0,
hydratedComponents: 0,
dehydratedViewsRemoved: 0,
dehydratedViewsCleanupRuns: 0,
componentsSkippedHydration: 0,
deferBlocksWithIncrementalHydration: 0,
};
// Make sure to refer to ngDevMode as ['ngDevMode'] for closure.
const allowNgDevModeTrue = locationString.indexOf('ngDevMode=false') === -1;
if (!allowNgDevModeTrue) {
_global['ngDevMode'] = false;
}
else {
if (typeof _global['ngDevMode'] !== 'object') {
_global['ngDevMode'] = {};
}
Object.assign(_global['ngDevMode'], newCounters);
}
return newCounters;
}
/**
* This function checks to see if the `ngDevMode` has been set. If yes,
* then we honor it, otherwise we default to dev mode with additional checks.
*
* The idea is that unless we are doing production build where we explicitly
* set `ngDevMode == false` we should be helping the developer by providing
* as much early warning and errors as possible.
*
* `ɵɵdefineComponent` is guaranteed to have been called before any component template functions
* (and thus Ivy instructions), so a single initialization there is sufficient to ensure ngDevMode
* is defined for the entire instruction set.
*
* When checking `ngDevMode` on toplevel, always init it before referencing it
* (e.g. `((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode())`), otherwise you can
* get a `ReferenceError` like in https://github.com/angular/angular/issues/31595.
*
* Details on possible values for `ngDevMode` can be found on its docstring.
*/
function initNgDevMode() {
// The below checks are to ensure that calling `initNgDevMode` multiple times does not
// reset the counters.
// If the `ngDevMode` is not an object, then it means we have not created the perf counters
// yet.
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (typeof ngDevMode !== 'object' || Object.keys(ngDevMode).length === 0) {
ngDevModeResetPerfCounters();
}
return typeof ngDevMode !== 'undefined' && !!ngDevMode;
}
return false;
}
function getClosureSafeProperty(objWithPropertyToExtract) {
for (let key in objWithPropertyToExtract) {
if (objWithPropertyToExtract[key] === getClosureSafeProperty) {
return key;
}
}
// Cannot change it to `RuntimeError` because the `util` target cannot
// circularly depend on the `core` target.
throw Error(typeof ngDevMode !== 'undefined' && ngDevMode
? 'Could not find renamed property on target object.'
: '');
}
/**
* Sets properties on a target object from a source object, but only if
* the property doesn't already exist on the target object.
* @param target The target to set properties on
* @param source The source of the property keys and values to set
*/
function fillProperties(target, source) {
for (const key in source) {
if (source.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}
function stringify(token) {
if (typeof token === 'string') {
return token;
}
if (Array.isArray(token)) {
return `[${token.map(stringify).join(', ')}]`;
}
if (token == null) {
return '' + token;
}
const name = token.overriddenName || token.name;
if (name) {
return `${name}`;
}
const result = token.toString();
if (result == null) {
return '' + result;
}
const newLineIndex = result.indexOf('\n');
return newLineIndex >= 0 ? result.slice(0, newLineIndex) : result;
}
/**
* Concatenates two strings with separator, allocating new strings only when necessary.
*
* @param before before string.
* @param separator separator string.
* @param after after string.
* @returns concatenated string.
*/
function concatStringsWithSpace(before, after) {
if (!before)
return after || '';
if (!after)
return before;
return `${before} ${after}`;
}
/**
* Ellipses the string in the middle when longer than the max length
*
* @param string
* @param maxLength of the output string
* @returns ellipsed string with ... in the middle
*/
function truncateMiddle(str, maxLength = 100) {
if (!str || maxLength < 1 || str.length <= maxLength)
return str;
if (maxLength == 1)
return str.substring(0, 1) + '...';
const halfLimit = Math.round(maxLength / 2);
return str.substring(0, halfLimit) + '...' + str.substring(str.length - halfLimit);
}
const __forward_ref__ = getClosureSafeProperty({ __forward_ref__: getClosureSafeProperty });
/**
* Allows to refer to references which are not yet defined.
*
* For instance, `forwardRef` is used when the `token` which we need to refer to for the purposes of
* DI is declared, but not yet defined. It is also used when the `token` which we use when creating
* a query is not yet defined.
*
* `forwardRef` is also used to break circularities in standalone components imports.
*
* @usageNotes
* ### Circular dependency example
* {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref'}
*
* ### Circular standalone reference import example
* ```angular-ts
* @Component({
* standalone: true,
* imports: [ChildComponent],
* selector: 'app-parent',
* template: ``,
* })
* export class ParentComponent {
* @Input() hideParent: boolean;
* }
*
*
* @Component({
* standalone: true,
* imports: [CommonModule, forwardRef(() => ParentComponent)],
* selector: 'app-child',
* template: ``,
* })
* export class ChildComponent {
* @Input() hideParent: boolean;
* }
* ```
*
* @publicApi
*/
function forwardRef(forwardRefFn) {
forwardRefFn.__forward_ref__ = forwardRef;
forwardRefFn.toString = function () {
return stringify(this());
};
return forwardRefFn;
}
/**
* Lazily retrieves the reference value from a forwardRef.
*
* Acts as the identity function when given a non-forward-ref value.
*
* @usageNotes
* ### Example
*
* {@example core/di/ts/forward_ref/forward_ref_spec.ts region='resolve_forward_ref'}
*
* @see {@link forwardRef}
* @publicApi
*/
function resolveForwardRef(type) {
return isForwardRef(type) ? type() : type;
}
/** Checks whether a function is wrapped by a `forwardRef`. */
function isForwardRef(fn) {
return (typeof fn === 'function' &&
fn.hasOwnProperty(__forward_ref__) &&
fn.__forward_ref__ === forwardRef);
}
// The functions in this file verify that the assumptions we are making
// about state in an instruction are correct before implementing any logic.
// They are meant only to be called in dev mode as sanity checks.
function assertNumber(actual, msg) {
if (!(typeof actual === 'number')) {
throwError(msg, typeof actual, 'number', '===');
}
}
function assertNumberInRange(actual, minInclusive, maxInclusive) {
assertNumber(actual, 'Expected a number');
assertLessThanOrEqual(actual, maxInclusive, 'Expected number to be less than or equal to');
assertGreaterThanOrEqual(actual, minInclusive, 'Expected number to be greater than or equal to');
}
function assertString(actual, msg) {
if (!(typeof actual === 'string')) {
throwError(msg, actual === null ? 'null' : typeof actual, 'string', '===');
}
}
function assertFunction(actual, msg) {
if (!(typeof actual === 'function')) {
throwError(msg, actual === null ? 'null' : typeof actual, 'function', '===');
}
}
function assertEqual(actual, expected, msg) {
if (!(actual == expected)) {
throwError(msg, actual, expected, '==');
}
}
function assertNotEqual(actual, expected, msg) {
if (!(actual != expected)) {
throwError(msg, actual, expected, '!=');
}
}
function assertSame(actual, expected, msg) {
if (!(actual === expected)) {
throwError(msg, actual, expected, '===');
}
}
function assertNotSame(actual, expected, msg) {
if (!(actual !== expected)) {
throwError(msg, actual, expected, '!==');
}
}
function assertLessThan(actual, expected, msg) {
if (!(actual < expected)) {
throwError(msg, actual, expected, '<');
}
}
function assertLessThanOrEqual(actual, expected, msg) {
if (!(actual <= expected)) {
throwError(msg, actual, expected, '<=');
}
}
function assertGreaterThan(actual, expected, msg) {
if (!(actual > expected)) {
throwError(msg, actual, expected, '>');
}
}
function assertGreaterThanOrEqual(actual, expected, msg) {
if (!(actual >= expected)) {
throwError(msg, actual, expected, '>=');
}
}
function assertNotDefined(actual, msg) {
if (actual != null) {
throwError(msg, actual, null, '==');
}
}
function assertDefined(actual, msg) {
if (actual == null) {
throwError(msg, actual, null, '!=');
}
}
function throwError(msg, actual, expected, comparison) {
throw new Error(`ASSERTION ERROR: ${msg}` +
(comparison == null ? '' : ` [Expected=> ${expected} ${comparison} ${actual} <=Actual]`));
}
function assertDomNode(node) {
if (!(node instanceof Node)) {
throwError(`The provided value must be an instance of a DOM Node but got ${stringify(node)}`);
}
}
function assertElement(node) {
if (!(node instanceof Element)) {
throwError(`The provided value must be an element but got ${stringify(node)}`);
}
}
function assertIndexInRange(arr, index) {
assertDefined(arr, 'Array must be defined.');
const maxLen = arr.length;
if (index < 0 || index >= maxLen) {
throwError(`Index expected to be less than ${maxLen} but got ${index}`);
}
}
function assertOneOf(value, ...validValues) {
if (validValues.indexOf(value) !== -1)
return true;
throwError(`Expected value to be one of ${JSON.stringify(validValues)} but was ${JSON.stringify(value)}.`);
}
function assertNotReactive(fn) {
if (getActiveConsumer() !== null) {
throwError(`${fn}() should never be called in a reactive context.`);
}
}
/**
* Construct an injectable definition which defines how a token will be constructed by the DI
* system, and in which injectors (if any) it will be available.
*
* This should be assigned to a static `ɵprov` field on a type, which will then be an
* `InjectableType`.
*
* Options:
* * `providedIn` determines which injectors will include the injectable, by either associating it
* with an `@NgModule` or other `InjectorType`, or by specifying that this injectable should be
* provided in the `'root'` injector, which will be the application-level injector in most apps.
* * `factory` gives the zero argument function which will create an instance of the injectable.
* The factory can call [`inject`](api/core/inject) to access the `Injector` and request injection
* of dependencies.
*
* @codeGenApi
* @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm.
*/
function ɵɵdefineInjectable(opts) {
return {
token: opts.token,
providedIn: opts.providedIn || null,
factory: opts.factory,
value: undefined,
};
}
/**
* @deprecated in v8, delete after v10. This API should be used only by generated code, and that
* code should now use ɵɵdefineInjectable instead.
* @publicApi
*/
const defineInjectable = ɵɵdefineInjectable;
/**
* Construct an `InjectorDef` which configures an injector.
*
* This should be assigned to a static injector def (`ɵinj`) field on a type, which will then be an
* `InjectorType`.
*
* Options:
*
* * `providers`: an optional array of providers to add to the injector. Each provider must
* either have a factory or point to a type which has a `ɵprov` static property (the
* type must be an `InjectableType`).
* * `imports`: an optional array of imports of other `InjectorType`s or `InjectorTypeWithModule`s
* whose providers will also be added to the injector. Locally provided types will override
* providers from imports.
*
* @codeGenApi
*/
function ɵɵdefineInjector(options) {
return { providers: options.providers || [], imports: options.imports || [] };
}
/**
* Read the injectable def (`ɵprov`) for `type` in a way which is immune to accidentally reading
* inherited value.
*
* @param type A type which may have its own (non-inherited) `ɵprov`.
*/
function getInjectableDef(type) {
return getOwnDefinition(type, NG_PROV_DEF);
}
function isInjectable(type) {
return getInjectableDef(type) !== null;
}
/**
* Return definition only if it is defined directly on `type` and is not inherited from a base
* class of `type`.
*/
function getOwnDefinition(type, field) {
// if the ɵprov prop exist but is undefined we still want to return null
return (type.hasOwnProperty(field) && type[field]) || null;
}
/**
* Read the injectable def (`ɵprov`) for `type` or read the `ɵprov` from one of its ancestors.
*
* @param type A type which may have `ɵprov`, via inheritance.
*
* @deprecated Will be removed in a future version of Angular, where an error will occur in the
* scenario if we find the `ɵprov` on an ancestor only.
*/
function getInheritedInjectableDef(type) {
// if the ɵprov prop exist but is undefined we still want to return null
const def = type?.[NG_PROV_DEF] ?? null;
if (def) {
ngDevMode &&
console.warn(`DEPRECATED: DI is instantiating a token "${type.name}" that inherits its @Injectable decorator but does not provide one itself.\n` +
`This will become an error in a future version of Angular. Please add @Injectable() to the "${type.name}" class.`);
return def;
}
else {
return null;
}
}
/**
* Read the injector def type in a way which is immune to accidentally reading inherited value.
*
* @param type type which may have an injector def (`ɵinj`)
*/
function getInjectorDef(type) {
return type && type.hasOwnProperty(NG_INJ_DEF) ? type[NG_INJ_DEF] : null;
}
const NG_PROV_DEF = getClosureSafeProperty({ ɵprov: getClosureSafeProperty });
const NG_INJ_DEF = getClosureSafeProperty({ ɵinj: getClosureSafeProperty });
/**
* Creates a token that can be used in a DI Provider.
*
* Use an `InjectionToken` whenever the type you are injecting is not reified (does not have a
* runtime representation) such as when injecting an interface, callable type, array or
* parameterized type.
*
* `InjectionToken` is parameterized on `T` which is the type of object which will be returned by
* the `Injector`. This provides an additional level of type safety.
*
*
*
* **Important Note**: Ensure that you use the same instance of the `InjectionToken` in both the
* provider and the injection call. Creating a new instance of `InjectionToken` in different places,
* even with the same description, will be treated as different tokens by Angular's DI system,
* leading to a `NullInjectorError`.
*
*
*
* {@example injection-token/src/main.ts region='InjectionToken'}
*
* When creating an `InjectionToken`, you can optionally specify a factory function which returns
* (possibly by creating) a default value of the parameterized type `T`. This sets up the
* `InjectionToken` using this factory as a provider as if it was defined explicitly in the
* application's root injector. If the factory function, which takes zero arguments, needs to inject
* dependencies, it can do so using the [`inject`](api/core/inject) function.
* As you can see in the Tree-shakable InjectionToken example below.
*
* Additionally, if a `factory` is specified you can also specify the `providedIn` option, which
* overrides the above behavior and marks the token as belonging to a particular `@NgModule` (note:
* this option is now deprecated). As mentioned above, `'root'` is the default value for
* `providedIn`.
*
* The `providedIn: NgModule` and `providedIn: 'any'` options are deprecated.
*
* @usageNotes
* ### Basic Examples
*
* ### Plain InjectionToken
*
* {@example core/di/ts/injector_spec.ts region='InjectionToken'}
*
* ### Tree-shakable InjectionToken
*
* {@example core/di/ts/injector_spec.ts region='ShakableInjectionToken'}
*
* @publicApi
*/
class InjectionToken {
_desc;
/** @internal */
ngMetadataName = 'InjectionToken';
ɵprov;
/**
* @param _desc Description for the token,
* used only for debugging purposes,
* it should but does not need to be unique
* @param options Options for the token's usage, as described above
*/
constructor(_desc, options) {
this._desc = _desc;
this.ɵprov = undefined;
if (typeof options == 'number') {
(typeof ngDevMode === 'undefined' || ngDevMode) &&
assertLessThan(options, 0, 'Only negative numbers are supported here');
// This is a special hack to assign __NG_ELEMENT_ID__ to this instance.
// See `InjectorMarkers`
this.__NG_ELEMENT_ID__ = options;
}
else if (options !== undefined) {
this.ɵprov = ɵɵdefineInjectable({
token: this,
providedIn: options.providedIn || 'root',
factory: options.factory,
});
}
}
/**
* @internal
*/
get multi() {
return this;
}
toString() {
return `InjectionToken ${this._desc}`;
}
}
let _injectorProfilerContext;
function getInjectorProfilerContext() {
!ngDevMode && throwError('getInjectorProfilerContext should never be called in production mode');
return _injectorProfilerContext;
}
function setInjectorProfilerContext(context) {
!ngDevMode && throwError('setInjectorProfilerContext should never be called in production mode');
const previous = _injectorProfilerContext;
_injectorProfilerContext = context;
return previous;
}
const injectorProfilerCallbacks = [];
const NOOP_PROFILER_REMOVAL = () => { };
function removeProfiler(profiler) {
const profilerIdx = injectorProfilerCallbacks.indexOf(profiler);
if (profilerIdx !== -1) {
injectorProfilerCallbacks.splice(profilerIdx, 1);
}
}
/**
* Adds a callback function which will be invoked during certain DI events within the
* runtime (for example: injecting services, creating injectable instances, configuring providers).
* Multiple profiler callbacks can be set: in this case profiling events are
* reported to every registered callback.
*
* Warning: this function is *INTERNAL* and should not be relied upon in application's code.
* The contract of the function might be changed in any release and/or the function can be removed
* completely.
*
* @param profiler function provided by the caller or null value to disable profiling.
* @returns a cleanup function that, when invoked, removes a given profiler callback.
*/
function setInjectorProfiler(injectorProfiler) {
!ngDevMode && throwError('setInjectorProfiler should never be called in production mode');
if (injectorProfiler !== null) {
if (!injectorProfilerCallbacks.includes(injectorProfiler)) {
injectorProfilerCallbacks.push(injectorProfiler);
}
return () => removeProfiler(injectorProfiler);
}
else {
injectorProfilerCallbacks.length = 0;
return NOOP_PROFILER_REMOVAL;
}
}
/**
* Injector profiler function which emits on DI events executed by the runtime.
*
* @param event InjectorProfilerEvent corresponding to the DI event being emitted
*/
function injectorProfiler(event) {
!ngDevMode && throwError('Injector profiler should never be called in production mode');
for (let i = 0; i < injectorProfilerCallbacks.length; i++) {
const injectorProfilerCallback = injectorProfilerCallbacks[i];
injectorProfilerCallback(event);
}
}
/**
* Emits an InjectorProfilerEventType.ProviderConfigured to the injector profiler. The data in the
* emitted event includes the raw provider, as well as the token that provider is providing.
*
* @param eventProvider A provider object
*/
function emitProviderConfiguredEvent(eventProvider, isViewProvider = false) {
!ngDevMode && throwError('Injector profiler should never be called in production mode');
let token;
// if the provider is a TypeProvider (typeof provider is function) then the token is the
// provider itself
if (typeof eventProvider === 'function') {
token = eventProvider;
}
// if the provider is an injection token, then the token is the injection token.
else if (eventProvider instanceof InjectionToken) {
token = eventProvider;
}
// in all other cases we can access the token via the `provide` property of the provider
else {
token = resolveForwardRef(eventProvider.provide);
}
let provider = eventProvider;
// Injection tokens may define their own default provider which gets attached to the token itself
// as `ɵprov`. In this case, we want to emit the provider that is attached to the token, not the
// token itself.
if (eventProvider instanceof InjectionToken) {
provider = eventProvider.ɵprov || eventProvider;
}
injectorProfiler({
type: 2 /* InjectorProfilerEventType.ProviderConfigured */,
context: getInjectorProfilerContext(),
providerRecord: { token, provider, isViewProvider },
});
}
/**
* Emits an event to the injector profiler when an instance corresponding to a given token is about to be created be an injector. Note that
* the injector associated with this emission can be accessed by using getDebugInjectContext()
*
* @param instance an object created by an injector
*/
function emitInjectorToCreateInstanceEvent(token) {
!ngDevMode && throwError('Injector profiler should never be called in production mode');
injectorProfiler({
type: 4 /* InjectorProfilerEventType.InjectorToCreateInstanceEvent */,
context: getInjectorProfilerContext(),
token: token,
});
}
/**
* Emits an event to the injector profiler with the instance that was created. Note that
* the injector associated with this emission can be accessed by using getDebugInjectContext()
*
* @param instance an object created by an injector
*/
function emitInstanceCreatedByInjectorEvent(instance) {
!ngDevMode && throwError('Injector profiler should never be called in production mode');
injectorProfiler({
type: 1 /* InjectorProfilerEventType.InstanceCreatedByInjector */,
context: getInjectorProfilerContext(),
instance: { value: instance },
});
}
/**
* @param token DI token associated with injected service
* @param value the instance of the injected service (i.e the result of `inject(token)`)
* @param flags the flags that the token was injected with
*/
function emitInjectEvent(token, value, flags) {
!ngDevMode && throwError('Injector profiler should never be called in production mode');
injectorProfiler({
type: 0 /* InjectorProfilerEventType.Inject */,
context: getInjectorProfilerContext(),
service: { token, value, flags },
});
}
function emitEffectCreatedEvent(effect) {
!ngDevMode && throwError('Injector profiler should never be called in production mode');
injectorProfiler({
type: 3 /* InjectorProfilerEventType.EffectCreated */,
context: getInjectorProfilerContext(),
effect,
});
}
function runInInjectorProfilerContext(injector, token, callback) {
!ngDevMode &&
throwError('runInInjectorProfilerContext should never be called in production mode');
const prevInjectContext = setInjectorProfilerContext({ injector, token });
try {
callback();
}
finally {
setInjectorProfilerContext(prevInjectContext);
}
}
function isEnvironmentProviders(value) {
return value && !!value.ɵproviders;
}
const NG_COMP_DEF = getClosureSafeProperty({ ɵcmp: getClosureSafeProperty });
const NG_DIR_DEF = getClosureSafeProperty({ ɵdir: getClosureSafeProperty });
const NG_PIPE_DEF = getClosureSafeProperty({ ɵpipe: getClosureSafeProperty });
const NG_MOD_DEF = getClosureSafeProperty({ ɵmod: getClosureSafeProperty });
const NG_FACTORY_DEF = getClosureSafeProperty({ ɵfac: getClosureSafeProperty });
/**
* If a directive is diPublic, bloomAdd sets a property on the type with this constant as
* the key and the directive's unique ID as the value. This allows us to map directives to their
* bloom filter bit for DI.
*/
// TODO(misko): This is wrong. The NG_ELEMENT_ID should never be minified.
const NG_ELEMENT_ID = getClosureSafeProperty({
__NG_ELEMENT_ID__: getClosureSafeProperty,
});
/**
* The `NG_ENV_ID` field on a DI token indicates special processing in the `EnvironmentInjector`:
* getting such tokens from the `EnvironmentInjector` will bypass the standard DI resolution
* strategy and instead will return implementation produced by the `NG_ENV_ID` factory function.
*
* This particular retrieval of DI tokens is mostly done to eliminate circular dependencies and
* improve tree-shaking.
*/
const NG_ENV_ID = getClosureSafeProperty({ __NG_ENV_ID__: getClosureSafeProperty });
/**
* Used for stringify render output in Ivy.
* Important! This function is very performance-sensitive and we should
* be extra careful not to introduce megamorphic reads in it.
* Check `core/test/render3/perf/render_stringify` for benchmarks and alternate implementations.
*/
function renderStringify(value) {
if (typeof value === 'string')
return value;
if (value == null)
return '';
// Use `String` so that it invokes the `toString` method of the value. Note that this
// appears to be faster than calling `value.toString` (see `render_stringify` benchmark).
return String(value);
}
/**
* Used to stringify a value so that it can be displayed in an error message.
*
* Important! This function contains a megamorphic read and should only be
* used for error messages.
*/
function stringifyForError(value) {
if (typeof value === 'function')
return value.name || value.toString();
if (typeof value === 'object' && value != null && typeof value.type === 'function') {
return value.type.name || value.type.toString();
}
return renderStringify(value);
}
/**
* Used to stringify a `Type` and including the file path and line number in which it is defined, if
* possible, for better debugging experience.
*
* Important! This function contains a megamorphic read and should only be used for error messages.
*/
function debugStringifyTypeForError(type) {
// TODO(pmvald): Do some refactoring so that we can use getComponentDef here without creating
// circular deps.
let componentDef = type[NG_COMP_DEF] || null;
if (componentDef !== null && componentDef.debugInfo) {
return stringifyTypeFromDebugInfo(componentDef.debugInfo);
}
return stringifyForError(type);
}
// TODO(pmvald): Do some refactoring so that we can use the type ClassDebugInfo for the param
// debugInfo here without creating circular deps.
function stringifyTypeFromDebugInfo(debugInfo) {
if (!debugInfo.filePath || !debugInfo.lineNumber) {
return debugInfo.className;
}
else {
return `${debugInfo.className} (at ${debugInfo.filePath}:${debugInfo.lineNumber})`;
}
}
/** Called when directives inject each other (creating a circular dependency) */
function throwCyclicDependencyError(token, path) {
throw new RuntimeError(-200 /* RuntimeErrorCode.CYCLIC_DI_DEPENDENCY */, ngDevMode
? `Circular dependency in DI detected for ${token}${path ? `. Dependency path: ${path.join(' > ')} > ${token}` : ''}`
: token);
}
function throwMixedMultiProviderError() {
throw new Error(`Cannot mix multi providers and regular providers`);
}
function throwInvalidProviderError(ngModuleType, providers, provider) {
if (ngModuleType && providers) {
const providerDetail = providers.map((v) => (v == provider ? '?' + provider + '?' : '...'));
throw new Error(`Invalid provider for the NgModule '${stringify(ngModuleType)}' - only instances of Provider and Type are allowed, got: [${providerDetail.join(', ')}]`);
}
else if (isEnvironmentProviders(provider)) {
if (provider.ɵfromNgModule) {
throw new RuntimeError(207 /* RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT */, `Invalid providers from 'importProvidersFrom' present in a non-environment injector. 'importProvidersFrom' can't be used for component providers.`);
}
else {
throw new RuntimeError(207 /* RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT */, `Invalid providers present in a non-environment injector. 'EnvironmentProviders' can't be used for component providers.`);
}
}
else {
throw new Error('Invalid provider');
}
}
/** Throws an error when a token is not found in DI. */
function throwProviderNotFoundError(token, injectorName) {
const errorMessage = ngDevMode &&
`No provider for ${stringifyForError(token)} found${injectorName ? ` in ${injectorName}` : ''}`;
throw new RuntimeError(-201 /* RuntimeErrorCode.PROVIDER_NOT_FOUND */, errorMessage);
}
/**
* Current implementation of inject.
*
* By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed
* to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this
* way for two reasons:
* 1. `Injector` should not depend on ivy logic.
* 2. To maintain tree shake-ability we don't want to bring in unnecessary code.
*/
let _injectImplementation;
function getInjectImplementation() {
return _injectImplementation;
}
/**
* Sets the current inject implementation.
*/
function setInjectImplementation(impl) {
const previous = _injectImplementation;
_injectImplementation = impl;
return previous;
}
/**
* Injects `root` tokens in limp mode.
*
* If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to
* `"root"`. This is known as the limp mode injection. In such case the value is stored in the
* injectable definition.
*/
function injectRootLimpMode(token, notFoundValue, flags) {
const injectableDef = getInjectableDef(token);
if (injectableDef && injectableDef.providedIn == 'root') {
return injectableDef.value === undefined
? (injectableDef.value = injectableDef.factory())
: injectableDef.value;
}
if (flags & 8 /* InternalInjectFlags.Optional */)
return null;
if (notFoundValue !== undefined)
return notFoundValue;
throwProviderNotFoundError(token, 'Injector');
}
/**
* Assert that `_injectImplementation` is not `fn`.
*
* This is useful, to prevent infinite recursion.
*
* @param fn Function which it should not equal to
*/
function assertInjectImplementationNotEqual(fn) {
ngDevMode &&
assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion');
}
const _THROW_IF_NOT_FOUND = {};
const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
/*
* Name of a property (that we patch onto DI decorator), which is used as an annotation of which
* InjectFlag this decorator represents. This allows to avoid direct references to the DI decorators
* in the code, thus making them tree-shakable.
*/
const DI_DECORATOR_FLAG = '__NG_DI_FLAG__';
/**
* A wrapper around an `Injector` that implements the `PrimitivesInjector` interface.
*
* This is used to allow the `inject` function to be used with the new primitives-based DI system.
*/
class RetrievingInjector {
injector;
constructor(injector) {
this.injector = injector;
}
retrieve(token, options) {
const flags = convertToBitFlags(options) || 0 /* InternalInjectFlags.Default */;
try {
return this.injector.get(token,
// When a dependency is requested with an optional flag, DI returns null as the default value.
(flags & 8 /* InternalInjectFlags.Optional */ ? null : THROW_IF_NOT_FOUND), flags);
}
catch (e) {
if (isNotFound(e)) {
return e;
}
throw e;
}
}
}
const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
const NG_TOKEN_PATH = 'ngTokenPath';
const NEW_LINE = /\n/gm;
const NO_NEW_LINE = 'ɵ';
const SOURCE = '__source';
function injectInjectorOnly(token, flags = 0 /* InternalInjectFlags.Default */) {
const currentInjector = getCurrentInjector();
if (currentInjector === undefined) {
throw new RuntimeError(-203 /* RuntimeErrorCode.MISSING_INJECTION_CONTEXT */, ngDevMode &&
`The \`${stringify(token)}\` token injection failed. \`inject()\` function must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with \`runInInjectionContext\`.`);
}
else if (currentInjector === null) {
return injectRootLimpMode(token, undefined, flags);
}
else {
const options = convertToInjectOptions(flags);
const value = currentInjector.retrieve(token, options);
ngDevMode && emitInjectEvent(token, value, flags);
if (isNotFound(value)) {
if (options.optional) {
return null;
}
throw value;
}
return value;
}
}
function ɵɵinject(token, flags = 0 /* InternalInjectFlags.Default */) {
return (getInjectImplementation() || injectInjectorOnly)(resolveForwardRef(token), flags);
}
/**
* Throws an error indicating that a factory function could not be generated by the compiler for a
* particular class.
*
* The name of the class is not mentioned here, but will be in the generated factory function name
* and thus in the stack trace.
*
* @codeGenApi
*/
function ɵɵinvalidFactoryDep(index) {
throw new RuntimeError(202 /* RuntimeErrorCode.INVALID_FACTORY_DEPENDENCY */, ngDevMode &&
`This constructor is not compatible with Angular Dependency Injection because its dependency at index ${index} of the parameter list is invalid.
This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.
Please check that 1) the type for the parameter at index ${index} is correct and 2) the correct Angular decorators are defined for this class and its ancestors.`);
}
/**
* Injects a token from the currently active injector.
* `inject` is only supported in an [injection context](guide/di/dependency-injection-context). It
* can be used during:
* - Construction (via the `constructor`) of a class being instantiated by the DI system, such
* as an `@Injectable` or `@Component`.
* - In the initializer for fields of such classes.
* - In the factory function specified for `useFactory` of a `Provider` or an `@Injectable`.
* - In the `factory` function specified for an `InjectionToken`.
* - In a stackframe of a function call in a DI context
*
* @param token A token that represents a dependency that should be injected.
* @param flags Optional flags that control how injection is executed.
* The flags correspond to injection strategies that can be specified with
* parameter decorators `@Host`, `@Self`, `@SkipSelf`, and `@Optional`.
* @returns the injected value if operation is successful, `null` otherwise.
* @throws if called outside of a supported context.
*
* @usageNotes
* In practice the `inject()` calls are allowed in a constructor, a constructor parameter and a
* field initializer:
*
* ```ts
* @Injectable({providedIn: 'root'})
* export class Car {
* radio: Radio|undefined;
* // OK: field initializer
* spareTyre = inject(Tyre);
*
* constructor() {
* // OK: constructor body
* this.radio = inject(Radio);
* }
* }
* ```
*
* It is also legal to call `inject` from a provider's factory:
*
* ```ts
* providers: [
* {provide: Car, useFactory: () => {
* // OK: a class factory
* const engine = inject(Engine);
* return new Car(engine);
* }}
* ]
* ```
*
* Calls to the `inject()` function outside of the class creation context will result in error. Most
* notably, calls to `inject()` are disallowed after a class instance was created, in methods
* (including lifecycle hooks):
*
* ```ts
* @Component({ ... })
* export class CarComponent {
* ngOnInit() {
* // ERROR: too late, the component instance was already created
* const engine = inject(Engine);
* engine.start();
* }
* }
* ```
*
* @publicApi
*/
function inject(token, options) {
// The `as any` here _shouldn't_ be necessary, but without it JSCompiler
// throws a disambiguation error due to the multiple signatures.
return ɵɵinject(token, convertToBitFlags(options));
}
// Converts object-based DI flags (`InjectOptions`) to bit flags (`InjectFlags`).
function convertToBitFlags(flags) {
if (typeof flags === 'undefined' || typeof flags === 'number') {
return flags;
}
// While TypeScript doesn't accept it without a cast, bitwise OR with false-y values in
// JavaScript is a no-op. We can use that for a very codesize-efficient conversion from
// `InjectOptions` to `InjectFlags`.
return (0 /* InternalInjectFlags.Default */ | // comment to force a line break in the formatter
(flags.optional && 8 /* InternalInjectFlags.Optional */) |
(flags.host && 1 /* InternalInjectFlags.Host */) |
(flags.self && 2 /* InternalInjectFlags.Self */) |
(flags.skipSelf && 4 /* InternalInjectFlags.SkipSelf */));
}
// Converts bitflags to inject options
function convertToInjectOptions(flags) {
return {
optional: !!(flags & 8 /* InternalInjectFlags.Optional */),
host: !!(flags & 1 /* InternalInjectFlags.Host */),
self: !!(flags & 2 /* InternalInjectFlags.Self */),
skipSelf: !!(flags & 4 /* InternalInjectFlags.SkipSelf */),
};
}
function injectArgs(types) {
const args = [];
for (let i = 0; i < types.length; i++) {
const arg = resolveForwardRef(types[i]);
if (Array.isArray(arg)) {
if (arg.length === 0) {
throw new RuntimeError(900 /* RuntimeErrorCode.INVALID_DIFFER_INPUT */, ngDevMode && 'Arguments array must have arguments.');
}
let type = undefined;
let flags = 0 /* InternalInjectFlags.Default */;
for (let j = 0; j < arg.length; j++) {
const meta = arg[j];
const flag = getInjectFlag(meta);
if (typeof flag === 'number') {
// Special case when we handle @Inject decorator.
if (flag === -1 /* DecoratorFlags.Inject */) {
type = meta.token;
}
else {
flags |= flag;
}
}
else {
type = meta;
}
}
args.push(ɵɵinject(type, flags));
}
else {
args.push(ɵɵinject(arg));
}
}
return args;
}
/**
* Attaches a given InjectFlag to a given decorator using monkey-patching.
* Since DI decorators can be used in providers `deps` array (when provider is configured using
* `useFactory`) without initialization (e.g. `Host`) and as an instance (e.g. `new Host()`), we
* attach the flag to make it available both as a static property and as a field on decorator
* instance.
*
* @param decorator Provided DI decorator.
* @param flag InjectFlag that should be applied.
*/
function attachInjectFlag(decorator, flag) {
decorator[DI_DECORATOR_FLAG] = flag;
decorator.prototype[DI_DECORATOR_FLAG] = flag;
return decorator;
}
/**
* Reads monkey-patched property that contains InjectFlag attached to a decorator.
*
* @param token Token that may contain monkey-patched DI flags property.
*/
function getInjectFlag(token) {
return token[DI_DECORATOR_FLAG];
}
function catchInjectorError(e, token, injectorErrorName, source) {
const tokenPath = e[NG_TEMP_TOKEN_PATH];
if (token[SOURCE]) {
tokenPath.unshift(token[SOURCE]);
}
e.message = formatError('\n' + e.message, tokenPath, injectorErrorName, source);
e[NG_TOKEN_PATH] = tokenPath;
e[NG_TEMP_TOKEN_PATH] = null;
throw e;
}
function formatError(text, obj, injectorErrorName, source = null) {
text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.slice(2) : text;
let context = stringify(obj);
if (Array.isArray(obj)) {
context = obj.map(stringify).join(' -> ');
}
else if (typeof obj === 'object') {
let parts = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let value = obj[key];
parts.push(key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value)));
}
}
context = `{${parts.join(', ')}}`;
}
return `${injectorErrorName}${source ? '(' + source + ')' : ''}[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
}
function getFactoryDef(type, throwNotFound) {
const hasFactoryDef = type.hasOwnProperty(NG_FACTORY_DEF);
if (!hasFactoryDef && throwNotFound === true && ngDevMode) {
throw new Error(`Type ${stringify(type)} does not have 'ɵfac' property.`);
}
return hasFactoryDef ? type[NG_FACTORY_DEF] : null;
}
/**
* Determines if the contents of two arrays is identical
*
* @param a first array
* @param b second array
* @param identityAccessor Optional function for extracting stable object identity from a value in
* the array.
*/
function arrayEquals(a, b, identityAccessor) {
if (a.length !== b.length)
return false;
for (let i = 0; i < a.length; i++) {
let valueA = a[i];
let valueB = b[i];
if (identityAccessor) {
valueA = identityAccessor(valueA);
valueB = identityAccessor(valueB);
}
if (valueB !== valueA) {
return false;
}
}
return true;
}
/**
* Flattens an array.
*/
function flatten(list) {
return list.flat(Number.POSITIVE_INFINITY);
}
function deepForEach(input, fn) {
input.forEach((value) => (Array.isArray(value) ? deepForEach(value, fn) : fn(value)));
}
function addToArray(arr, index, value) {
// perf: array.push is faster than array.splice!
if (index >= arr.length) {
arr.push(value);
}
else {
arr.splice(index, 0, value);
}
}
function removeFromArray(arr, index) {
// perf: array.pop is faster than array.splice!
if (index >= arr.length - 1) {
return arr.pop();
}
else {
return arr.splice(index, 1)[0];
}
}
function newArray(size, value) {
const list = [];
for (let i = 0; i < size; i++) {
list.push(value);
}
return list;
}
/**
* Remove item from array (Same as `Array.splice()` but faster.)
*
* `Array.splice()` is not as fast because it has to allocate an array for the elements which were
* removed. This causes memory pressure and slows down code when most of the time we don't
* care about the deleted items array.
*
* https://jsperf.com/fast-array-splice (About 20x faster)
*
* @param array Array to splice
* @param index Index of element in array to remove.
* @param count Number of items to remove.
*/
function arraySplice(array, index, count) {
const length = array.length - count;
while (index < length) {
array[index] = array[index + count];
index++;
}
while (count--) {
array.pop(); // shrink the array
}
}
/**
* Same as `Array.splice2(index, 0, value1, value2)` but faster.
*
* `Array.splice()` is not fast because it has to allocate an array for the elements which were
* removed. This causes memory pressure and slows down code when most of the time we don't
* care about the deleted items array.
*
* @param array Array to splice.
* @param index Index in array where the `value` should be added.
* @param value1 Value to add to array.
* @param value2 Value to add to array.
*/
function arrayInsert2(array, index, value1, value2) {
ngDevMode && assertLessThanOrEqual(index, array.length, "Can't insert past array end.");
let end = array.length;
if (end == index) {
// inserting at the end.
array.push(value1, value2);
}
else if (end === 1) {
// corner case when we have less items in array than we have items to insert.
array.push(value2, array[0]);
array[0] = value1;
}
else {
end--;
array.push(array[end - 1], array[end]);
while (end > index) {
const previousEnd = end - 2;
array[end] = array[previousEnd];
end--;
}
array[index] = value1;
array[index + 1] = value2;
}
}
/**
* Set a `value` for a `key`.
*
* @param keyValueArray to modify.
* @param key The key to locate or create.
* @param value The value to set for a `key`.
* @returns index (always even) of where the value vas set.
*/
function keyValueArraySet(keyValueArray, key, value) {
let index = keyValueArrayIndexOf(keyValueArray, key);
if (index >= 0) {
// if we found it set it.
keyValueArray[index | 1] = value;
}
else {
index = ~index;
arrayInsert2(keyValueArray, index, key, value);
}
return index;
}
/**
* Retrieve a `value` for a `key` (on `undefined` if not found.)
*
* @param keyValueArray to search.
* @param key The key to locate.
* @return The `value` stored at the `key` location or `undefined if not found.
*/
function keyValueArrayGet(keyValueArray, key) {
const index = keyValueArrayIndexOf(keyValueArray, key);
if (index >= 0) {
// if we found it retrieve it.
return keyValueArray[index | 1];
}
return undefined;
}
/**
* Retrieve a `key` index value in the array or `-1` if not found.
*
* @param keyValueArray to search.
* @param key The key to locate.
* @returns index of where the key is (or should have been.)
* - positive (even) index if key found.
* - negative index if key not found. (`~index` (even) to get the index where it should have
* been inserted.)
*/
function keyValueArrayIndexOf(keyValueArray, key) {
return _arrayIndexOfSorted(keyValueArray, key, 1);
}
/**
* INTERNAL: Get an index of an `value` in a sorted `array` by grouping search by `shift`.
*
* NOTE:
* - This uses binary search algorithm for fast removals.
*
* @param array A sorted array to binary search.
* @param value The value to look for.
* @param shift grouping shift.
* - `0` means look at every location
* - `1` means only look at every other (even) location (the odd locations are to be ignored as
* they are values.)
* @returns index of the value.
* - positive index if value found.
* - negative index if value not found. (`~index` to get the value where it should have been
* inserted)
*/
function _arrayIndexOfSorted(array, value, shift) {
ngDevMode && assertEqual(Array.isArray(array), true, 'Expecting an array');
let start = 0;
let end = array.length >> shift;
while (end !== start) {
const middle = start + ((end - start) >> 1); // find the middle.
const current = array[middle << shift];
if (value === current) {
return middle << shift;
}
else if (current > value) {
end = middle;
}
else {
start = middle + 1; // We already searched middle so make it non-inclusive by adding 1
}
}
return ~(end << shift);
}
/**
* This file contains reuseable "empty" symbols that can be used as default return values
* in different parts of the rendering code. Because the same symbols are returned, this
* allows for identity checks against these values to be consistently used by the framework
* code.
*/
const EMPTY_OBJ = {};
const EMPTY_ARRAY = [];
// freezing the values prevents any code from accidentally inserting new values in
if ((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode()) {
// These property accesses can be ignored because ngDevMode will be set to false
// when optimizing code and the whole if statement will be dropped.
// tslint:disable-next-line:no-toplevel-property-access
Object.freeze(EMPTY_OBJ);
// tslint:disable-next-line:no-toplevel-property-access
Object.freeze(EMPTY_ARRAY);
}
/**
* A multi-provider token for initialization functions that will run upon construction of an
* environment injector.
*
* @deprecated from v19.0.0, use provideEnvironmentInitializer instead
*
* @see {@link provideEnvironmentInitializer}
*
* Note: As opposed to the `APP_INITIALIZER` token, the `ENVIRONMENT_INITIALIZER` functions are not awaited,
* hence they should not be `async`.
*
* @publicApi
*/
const ENVIRONMENT_INITIALIZER = new InjectionToken(ngDevMode ? 'ENVIRONMENT_INITIALIZER' : '');
/**
* An InjectionToken that gets the current `Injector` for `createInjector()`-style injectors.
*
* Requesting this token instead of `Injector` allows `StaticInjector` to be tree-shaken from a
* project.
*
* @publicApi
*/
const INJECTOR$1 = new InjectionToken(ngDevMode ? 'INJECTOR' : '',
// Disable tslint because this is const enum which gets inlined not top level prop access.
// tslint:disable-next-line: no-toplevel-property-access
-1 /* InjectorMarkers.Injector */);
const INJECTOR_DEF_TYPES = new InjectionToken(ngDevMode ? 'INJECTOR_DEF_TYPES' : '');
class NullInjector {
get(token, notFoundValue = THROW_IF_NOT_FOUND) {
if (notFoundValue === THROW_IF_NOT_FOUND) {
const error = new NotFoundError(`NullInjectorError: No provider for ${stringify(token)}!`);
throw error;
}
return notFoundValue;
}
}
function getNgModuleDef(type) {
return type[NG_MOD_DEF] || null;
}
function getNgModuleDefOrThrow(type) {
const ngModuleDef = getNgModuleDef(type);
if (!ngModuleDef) {
throw new RuntimeError(915 /* RuntimeErrorCode.MISSING_NG_MODULE_DEFINITION */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
`Type ${stringify(type)} does not have 'ɵmod' property.`);
}
return ngModuleDef;
}
/**
* The following getter methods retrieve the definition from the type. Currently the retrieval
* honors inheritance, but in the future we may change the rule to require that definitions are
* explicit. This would require some sort of migration strategy.
*/
function getComponentDef(type) {
return type[NG_COMP_DEF] || null;
}
function getDirectiveDefOrThrow(type) {
const def = getDirectiveDef(type);
if (!def) {
throw new RuntimeError(916 /* RuntimeErrorCode.MISSING_DIRECTIVE_DEFINITION */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
`Type ${stringify(type)} does not have 'ɵdir' property.`);
}
return def;
}
function getDirectiveDef(type) {
return type[NG_DIR_DEF] || null;
}
function getPipeDef(type) {
return type[NG_PIPE_DEF] || null;
}
/**
* Checks whether a given Component, Directive or Pipe is marked as standalone.
* This will return false if passed anything other than a Component, Directive, or Pipe class
* See [this guide](guide/components/importing) for additional information:
*
* @param type A reference to a Component, Directive or Pipe.
* @publicApi
*/
function isStandalone(type) {
const def = getComponentDef(type) || getDirectiveDef(type) || getPipeDef(type);
return def !== null && def.standalone;
}
/**
* Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally
* referenced in `@Component` in a component injector.
*
* @publicApi
*/
function makeEnvironmentProviders(providers) {
return {
ɵproviders: providers,
};
}
/**
* @description
* This function is used to provide initialization functions that will be executed upon construction
* of an environment injector.
*
* Note that the provided initializer is run in the injection context.
*
* Previously, this was achieved using the `ENVIRONMENT_INITIALIZER` token which is now deprecated.
*
* @see {@link ENVIRONMENT_INITIALIZER}
*
* @usageNotes
* The following example illustrates how to configure an initialization function using
* `provideEnvironmentInitializer()`
* ```ts
* createEnvironmentInjector(
* [
* provideEnvironmentInitializer(() => {
* console.log('environment initialized');
* }),
* ],
* parentInjector
* );
* ```
*
* @publicApi
*/
function provideEnvironmentInitializer(initializerFn) {
return makeEnvironmentProviders([
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: initializerFn,
},
]);
}
/**
* Collects providers from all NgModules and standalone components, including transitively imported
* ones.
*
* Providers extracted via `importProvidersFrom` are only usable in an application injector or
* another environment injector (such as a route injector). They should not be used in component
* providers.
*
* More information about standalone components can be found in [this
* guide](guide/components/importing).
*
* @usageNotes
* The results of the `importProvidersFrom` call can be used in the `bootstrapApplication` call:
*
* ```ts
* await bootstrapApplication(RootComponent, {
* providers: [
* importProvidersFrom(NgModuleOne, NgModuleTwo)
* ]
* });
* ```
*
* You can also use the `importProvidersFrom` results in the `providers` field of a route, when a
* standalone component is used:
*
* ```ts
* export const ROUTES: Route[] = [
* {
* path: 'foo',
* providers: [
* importProvidersFrom(NgModuleOne, NgModuleTwo)
* ],
* component: YourStandaloneComponent
* }
* ];
* ```
*
* @returns Collected providers from the specified list of types.
* @publicApi
*/
function importProvidersFrom(...sources) {
return {
ɵproviders: internalImportProvidersFrom(true, sources),
ɵfromNgModule: true,
};
}
function internalImportProvidersFrom(checkForStandaloneCmp, ...sources) {
const providersOut = [];
const dedup = new Set(); // already seen types
let injectorTypesWithProviders;
const collectProviders = (provider) => {
providersOut.push(provider);
};
deepForEach(sources, (source) => {
if ((typeof ngDevMode === 'undefined' || ngDevMode) && checkForStandaloneCmp) {
const cmpDef = getComponentDef(source);
if (cmpDef?.standalone) {
throw new RuntimeError(800 /* RuntimeErrorCode.IMPORT_PROVIDERS_FROM_STANDALONE */, `Importing providers supports NgModule or ModuleWithProviders but got a standalone component "${stringifyForError(source)}"`);
}
}
// Narrow `source` to access the internal type analogue for `ModuleWithProviders`.
const internalSource = source;
if (walkProviderTree(internalSource, collectProviders, [], dedup)) {
injectorTypesWithProviders ||= [];
injectorTypesWithProviders.push(internalSource);
}
});
// Collect all providers from `ModuleWithProviders` types.
if (injectorTypesWithProviders !== undefined) {
processInjectorTypesWithProviders(injectorTypesWithProviders, collectProviders);
}
return providersOut;
}
/**
* Collects all providers from the list of `ModuleWithProviders` and appends them to the provided
* array.
*/
function processInjectorTypesWithProviders(typesWithProviders, visitor) {
for (let i = 0; i < typesWithProviders.length; i++) {
const { ngModule, providers } = typesWithProviders[i];
deepForEachProvider(providers, (provider) => {
ngDevMode && validateProvider(provider, providers || EMPTY_ARRAY, ngModule);
visitor(provider, ngModule);
});
}
}
/**
* The logic visits an `InjectorType`, an `InjectorTypeWithProviders`, or a standalone
* `ComponentType`, and all of its transitive providers and collects providers.
*
* If an `InjectorTypeWithProviders` that declares providers besides the type is specified,
* the function will return "true" to indicate that the providers of the type definition need
* to be processed. This allows us to process providers of injector types after all imports of
* an injector definition are processed. (following View Engine semantics: see FW-1349)
*/
function walkProviderTree(container, visitor, parents, dedup) {
container = resolveForwardRef(container);
if (!container)
return false;
// The actual type which had the definition. Usually `container`, but may be an unwrapped type
// from `InjectorTypeWithProviders`.
let defType = null;
let injDef = getInjectorDef(container);
const cmpDef = !injDef && getComponentDef(container);
if (!injDef && !cmpDef) {
// `container` is not an injector type or a component type. It might be:
// * An `InjectorTypeWithProviders` that wraps an injector type.
// * A standalone directive or pipe that got pulled in from a standalone component's
// dependencies.
// Try to unwrap it as an `InjectorTypeWithProviders` first.
const ngModule = container
.ngModule;
injDef = getInjectorDef(ngModule);
if (injDef) {
defType = ngModule;
}
else {
// Not a component or injector type, so ignore it.
return false;
}
}
else if (cmpDef && !cmpDef.standalone) {
return false;
}
else {
defType = container;
}
// Check for circular dependencies.
if (ngDevMode && parents.indexOf(defType) !== -1) {
const defName = stringify(defType);
const path = parents.map(stringify);
throwCyclicDependencyError(defName, path);
}
// Check for multiple imports of the same module
const isDuplicate = dedup.has(defType);
if (cmpDef) {
if (isDuplicate) {
// This component definition has already been processed.
return false;
}
dedup.add(defType);
if (cmpDef.dependencies) {
const deps = typeof cmpDef.dependencies === 'function' ? cmpDef.dependencies() : cmpDef.dependencies;
for (const dep of deps) {
walkProviderTree(dep, visitor, parents, dedup);
}
}
}
else if (injDef) {
// First, include providers from any imports.
if (injDef.imports != null && !isDuplicate) {
// Before processing defType's imports, add it to the set of parents. This way, if it ends
// up deeply importing itself, this can be detected.
ngDevMode && parents.push(defType);
// Add it to the set of dedups. This way we can detect multiple imports of the same module
dedup.add(defType);
let importTypesWithProviders;
try {
deepForEach(injDef.imports, (imported) => {
if (walkProviderTree(imported, visitor, parents, dedup)) {
importTypesWithProviders ||= [];
// If the processed import is an injector type with providers, we store it in the
// list of import types with providers, so that we can process those afterwards.
importTypesWithProviders.push(imported);
}
});
}
finally {
// Remove it from the parents set when finished.
ngDevMode && parents.pop();
}
// Imports which are declared with providers (TypeWithProviders) need to be processed
// after all imported modules are processed. This is similar to how View Engine
// processes/merges module imports in the metadata resolver. See: FW-1349.
if (importTypesWithProviders !== undefined) {
processInjectorTypesWithProviders(importTypesWithProviders, visitor);
}
}
if (!isDuplicate) {
// Track the InjectorType and add a provider for it.
// It's important that this is done after the def's imports.
const factory = getFactoryDef(defType) || (() => new defType());
// Append extra providers to make more info available for consumers (to retrieve an injector
// type), as well as internally (to calculate an injection scope correctly and eagerly
// instantiate a `defType` when an injector is created).
// Provider to create `defType` using its factory.
visitor({ provide: defType, useFactory: factory, deps: EMPTY_ARRAY }, defType);
// Make this `defType` available to an internal logic that calculates injector scope.
visitor({ provide: INJECTOR_DEF_TYPES, useValue: defType, multi: true }, defType);
// Provider to eagerly instantiate `defType` via `INJECTOR_INITIALIZER`.
visitor({ provide: ENVIRONMENT_INITIALIZER, useValue: () => ɵɵinject(defType), multi: true }, defType);
}
// Next, include providers listed on the definition itself.
const defProviders = injDef.providers;
if (defProviders != null && !isDuplicate) {
const injectorType = container;
deepForEachProvider(defProviders, (provider) => {
ngDevMode && validateProvider(provider, defProviders, injectorType);
visitor(provider, injectorType);
});
}
}
else {
// Should not happen, but just in case.
return false;
}
return (defType !== container && container.providers !== undefined);
}
function validateProvider(provider, providers, containerType) {
if (isTypeProvider(provider) ||
isValueProvider(provider) ||
isFactoryProvider(provider) ||
isExistingProvider(provider)) {
return;
}
// Here we expect the provider to be a `useClass` provider (by elimination).
const classRef = resolveForwardRef(provider && (provider.useClass || provider.provide));
if (!classRef) {
throwInvalidProviderError(containerType, providers, provider);
}
}
function deepForEachProvider(providers, fn) {
for (let provider of providers) {
if (isEnvironmentProviders(provider)) {
provider = provider.ɵproviders;
}
if (Array.isArray(provider)) {
deepForEachProvider(provider, fn);
}
else {
fn(provider);
}
}
}
const USE_VALUE = getClosureSafeProperty({
provide: String,
useValue: getClosureSafeProperty,
});
function isValueProvider(value) {
return value !== null && typeof value == 'object' && USE_VALUE in value;
}
function isExistingProvider(value) {
return !!(value && value.useExisting);
}
function isFactoryProvider(value) {
return !!(value && value.useFactory);
}
function isTypeProvider(value) {
return typeof value === 'function';
}
function isClassProvider(value) {
return !!value.useClass;
}
/**
* An internal token whose presence in an injector indicates that the injector should treat itself
* as a root scoped injector when processing requests for unknown tokens which may indicate
* they are provided in the root scope.
*/
const INJECTOR_SCOPE = new InjectionToken(ngDevMode ? 'Set Injector scope.' : '');
/**
* Marker which indicates that a value has not yet been created from the factory function.
*/
const NOT_YET = {};
/**
* Marker which indicates that the factory function for a token is in the process of being called.
*
* If the injector is asked to inject a token with its value set to CIRCULAR, that indicates
* injection of a dependency has recursively attempted to inject the original token, and there is
* a circular dependency among the providers.
*/
const CIRCULAR = {};
/**
* A lazily initialized NullInjector.
*/
let NULL_INJECTOR = undefined;
function getNullInjector() {
if (NULL_INJECTOR === undefined) {
NULL_INJECTOR = new NullInjector();
}
return NULL_INJECTOR;
}
/**
* An `Injector` that's part of the environment injector hierarchy, which exists outside of the
* component tree.
*
* @publicApi
*/
class EnvironmentInjector {
}
class R3Injector extends EnvironmentInjector {
parent;
source;
scopes;
/**
* Map of tokens to records which contain the instances of those tokens.
* - `null` value implies that we don't have the record. Used by tree-shakable injectors
* to prevent further searches.
*/
records = new Map();
/**
* Set of values instantiated by this injector which contain `ngOnDestroy` lifecycle hooks.
*/
_ngOnDestroyHooks = new Set();
_onDestroyHooks = [];
/**
* Flag indicating that this injector was previously destroyed.
*/
get destroyed() {
return this._destroyed;
}
_destroyed = false;
injectorDefTypes;
constructor(providers, parent, source, scopes) {
super();
this.parent = parent;
this.source = source;
this.scopes = scopes;
// Start off by creating Records for every provider.
forEachSingleProvider(providers, (provider) => this.processProvider(provider));
// Make sure the INJECTOR token provides this injector.
this.records.set(INJECTOR$1, makeRecord(undefined, this));
// And `EnvironmentInjector` if the current injector is supposed to be env-scoped.
if (scopes.has('environment')) {
this.records.set(EnvironmentInjector, makeRecord(undefined, this));
}
// Detect whether this injector has the APP_ROOT_SCOPE token and thus should provide
// any injectable scoped to APP_ROOT_SCOPE.
const record = this.records.get(INJECTOR_SCOPE);
if (record != null && typeof record.value === 'string') {
this.scopes.add(record.value);
}
this.injectorDefTypes = new Set(this.get(INJECTOR_DEF_TYPES, EMPTY_ARRAY, { self: true }));
}
retrieve(token, options) {
const flags = convertToBitFlags(options) || 0 /* InternalInjectFlags.Default */;
try {
return this.get(token,
// When a dependency is requested with an optional flag, DI returns null as the default value.
THROW_IF_NOT_FOUND, flags);
}
catch (e) {
if (isNotFound$1(e)) {
return e;
}
throw e;
}
}
/**
* Destroy the injector and release references to every instance or provider associated with it.
*
* Also calls the `OnDestroy` lifecycle hooks of every instance that was created for which a
* hook was found.
*/
destroy() {
assertNotDestroyed(this);
// Set destroyed = true first, in case lifecycle hooks re-enter destroy().
this._destroyed = true;
const prevConsumer = setActiveConsumer(null);
try {
// Call all the lifecycle hooks.
for (const service of this._ngOnDestroyHooks) {
service.ngOnDestroy();
}
const onDestroyHooks = this._onDestroyHooks;
// Reset the _onDestroyHooks array before iterating over it to prevent hooks that unregister
// themselves from mutating the array during iteration.
this._onDestroyHooks = [];
for (const hook of onDestroyHooks) {
hook();
}
}
finally {
// Release all references.
this.records.clear();
this._ngOnDestroyHooks.clear();
this.injectorDefTypes.clear();
setActiveConsumer(prevConsumer);
}
}
onDestroy(callback) {
assertNotDestroyed(this);
this._onDestroyHooks.push(callback);
return () => this.removeOnDestroy(callback);
}
runInContext(fn) {
assertNotDestroyed(this);
const previousInjector = setCurrentInjector(this);
const previousInjectImplementation = setInjectImplementation(undefined);
let prevInjectContext;
if (ngDevMode) {
prevInjectContext = setInjectorProfilerContext({ injector: this, token: null });
}
try {
return fn();
}
finally {
setCurrentInjector(previousInjector);
setInjectImplementation(previousInjectImplementation);
ngDevMode && setInjectorProfilerContext(prevInjectContext);
}
}
get(token, notFoundValue = THROW_IF_NOT_FOUND, options) {
assertNotDestroyed(this);
if (token.hasOwnProperty(NG_ENV_ID)) {
return token[NG_ENV_ID](this);
}
const flags = convertToBitFlags(options);
// Set the injection context.
let prevInjectContext;
if (ngDevMode) {
prevInjectContext = setInjectorProfilerContext({ injector: this, token: token });
}
const previousInjector = setCurrentInjector(this);
const previousInjectImplementation = setInjectImplementation(undefined);
try {
// Check for the SkipSelf flag.
if (!(flags & 4 /* InternalInjectFlags.SkipSelf */)) {
// SkipSelf isn't set, check if the record belongs to this injector.
let record = this.records.get(token);
if (record === undefined) {
// No record, but maybe the token is scoped to this injector. Look for an injectable
// def with a scope matching this injector.
const def = couldBeInjectableType(token) && getInjectableDef(token);
if (def && this.injectableDefInScope(def)) {
// Found an injectable def and it's scoped to this injector. Pretend as if it was here
// all along.
if (ngDevMode) {
runInInjectorProfilerContext(this, token, () => {
emitProviderConfiguredEvent(token);
});
}
record = makeRecord(injectableDefOrInjectorDefFactory(token), NOT_YET);
}
else {
record = null;
}
this.records.set(token, record);
}
// If a record was found, get the instance for it and return it.
if (record != null /* NOT null || undefined */) {
return this.hydrate(token, record);
}
}
// Select the next injector based on the Self flag - if self is set, the next injector is
// the NullInjector, otherwise it's the parent.
const nextInjector = !(flags & 2 /* InternalInjectFlags.Self */) ? this.parent : getNullInjector();
// Set the notFoundValue based on the Optional flag - if optional is set and notFoundValue
// is undefined, the value is null, otherwise it's the notFoundValue.
notFoundValue =
flags & 8 /* InternalInjectFlags.Optional */ && notFoundValue === THROW_IF_NOT_FOUND
? null
: notFoundValue;
return nextInjector.get(token, notFoundValue);
}
catch (e) {
if (isNotFound$1(e)) {
// @ts-ignore
const path = (e[NG_TEMP_TOKEN_PATH] = e[NG_TEMP_TOKEN_PATH] || []);
path.unshift(stringify(token));
if (previousInjector) {
// We still have a parent injector, keep throwing
throw e;
}
else {
// Format & throw the final error message when we don't have any previous injector
return catchInjectorError(e, token, 'R3InjectorError', this.source);
}
}
else {
throw e;
}
}
finally {
// Lastly, restore the previous injection context.
setInjectImplementation(previousInjectImplementation);
setCurrentInjector(previousInjector);
ngDevMode && setInjectorProfilerContext(prevInjectContext);
}
}
/** @internal */
resolveInjectorInitializers() {
const prevConsumer = setActiveConsumer(null);
const previousInjector = setCurrentInjector(this);
const previousInjectImplementation = setInjectImplementation(undefined);
let prevInjectContext;
if (ngDevMode) {
prevInjectContext = setInjectorProfilerContext({ injector: this, token: null });
}
try {
const initializers = this.get(ENVIRONMENT_INITIALIZER, EMPTY_ARRAY, { self: true });
if (ngDevMode && !Array.isArray(initializers)) {
throw new RuntimeError(-209 /* RuntimeErrorCode.INVALID_MULTI_PROVIDER */, 'Unexpected type of the `ENVIRONMENT_INITIALIZER` token value ' +
`(expected an array, but got ${typeof initializers}). ` +
'Please check that the `ENVIRONMENT_INITIALIZER` token is configured as a ' +
'`multi: true` provider.');
}
for (const initializer of initializers) {
initializer();
}
}
finally {
setCurrentInjector(previousInjector);
setInjectImplementation(previousInjectImplementation);
ngDevMode && setInjectorProfilerContext(prevInjectContext);
setActiveConsumer(prevConsumer);
}
}
toString() {
const tokens = [];
const records = this.records;
for (const token of records.keys()) {
tokens.push(stringify(token));
}
return `R3Injector[${tokens.join(', ')}]`;
}
/**
* Process a `SingleProvider` and add it.
*/
processProvider(provider) {
// Determine the token from the provider. Either it's its own token, or has a {provide: ...}
// property.
provider = resolveForwardRef(provider);
let token = isTypeProvider(provider)
? provider
: resolveForwardRef(provider && provider.provide);
// Construct a `Record` for the provider.
const record = providerToRecord(provider);
if (ngDevMode) {
runInInjectorProfilerContext(this, token, () => {
// Emit InjectorProfilerEventType.Create if provider is a value provider because
// these are the only providers that do not go through the value hydration logic
// where this event would normally be emitted from.
if (isValueProvider(provider)) {
emitInjectorToCreateInstanceEvent(token);
emitInstanceCreatedByInjectorEvent(provider.useValue);
}
emitProviderConfiguredEvent(provider);
});
}
if (!isTypeProvider(provider) && provider.multi === true) {
// If the provider indicates that it's a multi-provider, process it specially.
// First check whether it's been defined already.
let multiRecord = this.records.get(token);
if (multiRecord) {
// It has. Throw a nice error if
if (ngDevMode && multiRecord.multi === undefined) {
throwMixedMultiProviderError();
}
}
else {
multiRecord = makeRecord(undefined, NOT_YET, true);
multiRecord.factory = () => injectArgs(multiRecord.multi);
this.records.set(token, multiRecord);
}
token = provider;
multiRecord.multi.push(provider);
}
else {
if (ngDevMode) {
const existing = this.records.get(token);
if (existing && existing.multi !== undefined) {
throwMixedMultiProviderError();
}
}
}
this.records.set(token, record);
}
hydrate(token, record) {
const prevConsumer = setActiveConsumer(null);
try {
if (record.value === CIRCULAR) {
throwCyclicDependencyError(stringify(token));
}
else if (record.value === NOT_YET) {
record.value = CIRCULAR;
if (ngDevMode) {
runInInjectorProfilerContext(this, token, () => {
emitInjectorToCreateInstanceEvent(token);
record.value = record.factory();
emitInstanceCreatedByInjectorEvent(record.value);
});
}
else {
record.value = record.factory();
}
}
if (typeof record.value === 'object' && record.value && hasOnDestroy(record.value)) {
this._ngOnDestroyHooks.add(record.value);
}
return record.value;
}
finally {
setActiveConsumer(prevConsumer);
}
}
injectableDefInScope(def) {
if (!def.providedIn) {
return false;
}
const providedIn = resolveForwardRef(def.providedIn);
if (typeof providedIn === 'string') {
return providedIn === 'any' || this.scopes.has(providedIn);
}
else {
return this.injectorDefTypes.has(providedIn);
}
}
removeOnDestroy(callback) {
const destroyCBIdx = this._onDestroyHooks.indexOf(callback);
if (destroyCBIdx !== -1) {
this._onDestroyHooks.splice(destroyCBIdx, 1);
}
}
}
function injectableDefOrInjectorDefFactory(token) {
// Most tokens will have an injectable def directly on them, which specifies a factory directly.
const injectableDef = getInjectableDef(token);
const factory = injectableDef !== null ? injectableDef.factory : getFactoryDef(token);
if (factory !== null) {
return factory;
}
// InjectionTokens should have an injectable def (ɵprov) and thus should be handled above.
// If it's missing that, it's an error.
if (token instanceof InjectionToken) {
throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode && `Token ${stringify(token)} is missing a ɵprov definition.`);
}
// Undecorated types can sometimes be created if they have no constructor arguments.
if (token instanceof Function) {
return getUndecoratedInjectableFactory(token);
}
// There was no way to resolve a factory for this token.
throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode && 'unreachable');
}
function getUndecoratedInjectableFactory(token) {
// If the token has parameters then it has dependencies that we cannot resolve implicitly.
const paramLength = token.length;
if (paramLength > 0) {
throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode &&
`Can't resolve all parameters for ${stringify(token)}: (${newArray(paramLength, '?').join(', ')}).`);
}
// The constructor function appears to have no parameters.
// This might be because it inherits from a super-class. In which case, use an injectable
// def from an ancestor if there is one.
// Otherwise this really is a simple class with no dependencies, so return a factory that
// just instantiates the zero-arg constructor.
const inheritedInjectableDef = getInheritedInjectableDef(token);
if (inheritedInjectableDef !== null) {
return () => inheritedInjectableDef.factory(token);
}
else {
return () => new token();
}
}
function providerToRecord(provider) {
if (isValueProvider(provider)) {
return makeRecord(undefined, provider.useValue);
}
else {
const factory = providerToFactory(provider);
return makeRecord(factory, NOT_YET);
}
}
/**
* Converts a `SingleProvider` into a factory function.
*
* @param provider provider to convert to factory
*/
function providerToFactory(provider, ngModuleType, providers) {
let factory = undefined;
if (ngDevMode && isEnvironmentProviders(provider)) {
throwInvalidProviderError(undefined, providers, provider);
}
if (isTypeProvider(provider)) {
const unwrappedProvider = resolveForwardRef(provider);
return getFactoryDef(unwrappedProvider) || injectableDefOrInjectorDefFactory(unwrappedProvider);
}
else {
if (isValueProvider(provider)) {
factory = () => resolveForwardRef(provider.useValue);
}
else if (isFactoryProvider(provider)) {
factory = () => provider.useFactory(...injectArgs(provider.deps || []));
}
else if (isExistingProvider(provider)) {
factory = () => ɵɵinject(resolveForwardRef(provider.useExisting));
}
else {
const classRef = resolveForwardRef(provider &&
(provider.useClass || provider.provide));
if (ngDevMode && !classRef) {
throwInvalidProviderError(ngModuleType, providers, provider);
}
if (hasDeps(provider)) {
factory = () => new classRef(...injectArgs(provider.deps));
}
else {
return getFactoryDef(classRef) || injectableDefOrInjectorDefFactory(classRef);
}
}
}
return factory;
}
function assertNotDestroyed(injector) {
if (injector.destroyed) {
throw new RuntimeError(205 /* RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED */, ngDevMode && 'Injector has already been destroyed.');
}
}
function makeRecord(factory, value, multi = false) {
return {
factory: factory,
value: value,
multi: multi ? [] : undefined,
};
}
function hasDeps(value) {
return !!value.deps;
}
function hasOnDestroy(value) {
return (value !== null &&
typeof value === 'object' &&
typeof value.ngOnDestroy === 'function');
}
function couldBeInjectableType(value) {
return (typeof value === 'function' ||
(typeof value === 'object' && value.ngMetadataName === 'InjectionToken'));
}
function forEachSingleProvider(providers, fn) {
for (const provider of providers) {
if (Array.isArray(provider)) {
forEachSingleProvider(provider, fn);
}
else if (provider && isEnvironmentProviders(provider)) {
forEachSingleProvider(provider.ɵproviders, fn);
}
else {
fn(provider);
}
}
}
/**
* Runs the given function in the [context](guide/di/dependency-injection-context) of the given
* `Injector`.
*
* Within the function's stack frame, [`inject`](api/core/inject) can be used to inject dependencies
* from the given `Injector`. Note that `inject` is only usable synchronously, and cannot be used in
* any asynchronous callbacks or after any `await` points.
*
* @param injector the injector which will satisfy calls to [`inject`](api/core/inject) while `fn`
* is executing
* @param fn the closure to be run in the context of `injector`
* @returns the return value of the function, if any
* @publicApi
*/
function runInInjectionContext(injector, fn) {
let internalInjector;
if (injector instanceof R3Injector) {
assertNotDestroyed(injector);
internalInjector = injector;
}
else {
internalInjector = new RetrievingInjector(injector);
}
let prevInjectorProfilerContext;
if (ngDevMode) {
prevInjectorProfilerContext = setInjectorProfilerContext({ injector, token: null });
}
const prevInjector = setCurrentInjector(internalInjector);
const previousInjectImplementation = setInjectImplementation(undefined);
try {
return fn();
}
finally {
setCurrentInjector(prevInjector);
ngDevMode && setInjectorProfilerContext(prevInjectorProfilerContext);
setInjectImplementation(previousInjectImplementation);
}
}
/**
* Whether the current stack frame is inside an injection context.
*/
function isInInjectionContext() {
return getInjectImplementation() !== undefined || getCurrentInjector() != null;
}
/**
* Asserts that the current stack frame is within an [injection
* context](guide/di/dependency-injection-context) and has access to `inject`.
*
* @param debugFn a reference to the function making the assertion (used for the error message).
*
* @publicApi
*/
function assertInInjectionContext(debugFn) {
// Taking a `Function` instead of a string name here prevents the unminified name of the function
// from being retained in the bundle regardless of minification.
if (!isInInjectionContext()) {
throw new RuntimeError(-203 /* RuntimeErrorCode.MISSING_INJECTION_CONTEXT */, ngDevMode &&
debugFn.name +
'() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`');
}
}
// Below are constants for LView indices to help us look up LView members
// without having to remember the specific indices.
// Uglify will inline these when minifying so there shouldn't be a cost.
const HOST = 0;
const TVIEW = 1;
// Shared with LContainer
const FLAGS = 2;
const PARENT = 3;
const NEXT = 4;
const T_HOST = 5;
// End shared with LContainer
const HYDRATION = 6;
const CLEANUP = 7;
const CONTEXT = 8;
const INJECTOR = 9;
const ENVIRONMENT = 10;
const RENDERER = 11;
const CHILD_HEAD = 12;
const CHILD_TAIL = 13;
// FIXME(misko): Investigate if the three declarations aren't all same thing.
const DECLARATION_VIEW = 14;
const DECLARATION_COMPONENT_VIEW = 15;
const DECLARATION_LCONTAINER = 16;
const PREORDER_HOOK_FLAGS = 17;
const QUERIES = 18;
const ID = 19;
const EMBEDDED_VIEW_INJECTOR = 20;
const ON_DESTROY_HOOKS = 21;
const EFFECTS_TO_SCHEDULE = 22;
const EFFECTS = 23;
const REACTIVE_TEMPLATE_CONSUMER = 24;
const AFTER_RENDER_SEQUENCES_TO_ADD = 25;
/**
* Size of LView's header. Necessary to adjust for it when setting slots.
*
* IMPORTANT: `HEADER_OFFSET` should only be referred to the in the `ɵɵ*` instructions to translate
* instruction index into `LView` index. All other indexes should be in the `LView` index space and
* there should be no need to refer to `HEADER_OFFSET` anywhere else.
*/
const HEADER_OFFSET = 26;
/**
* Special location which allows easy identification of type. If we have an array which was
* retrieved from the `LView` and that array has `true` at `TYPE` location, we know it is
* `LContainer`.
*/
const TYPE = 1;
/**
* Below are constants for LContainer indices to help us look up LContainer members
* without having to remember the specific indices.
* Uglify will inline these when minifying so there shouldn't be a cost.
*/
// FLAGS, PARENT, NEXT, and T_HOST are indices 2, 3, 4, and 5
// As we already have these constants in LView, we don't need to re-create them.
const DEHYDRATED_VIEWS = 6;
const NATIVE = 7;
const VIEW_REFS = 8;
const MOVED_VIEWS = 9;
/**
* Size of LContainer's header. Represents the index after which all views in the
* container will be inserted. We need to keep a record of current views so we know
* which views are already in the DOM (and don't need to be re-added) and so we can
* remove views from the DOM when they are no longer required.
*/
const CONTAINER_HEADER_OFFSET = 10;
/**
* True if `value` is `LView`.
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
function isLView(value) {
return Array.isArray(value) && typeof value[TYPE] === 'object';
}
/**
* True if `value` is `LContainer`.
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
function isLContainer(value) {
return Array.isArray(value) && value[TYPE] === true;
}
function isContentQueryHost(tNode) {
return (tNode.flags & 4 /* TNodeFlags.hasContentQuery */) !== 0;
}
function isComponentHost(tNode) {
return tNode.componentOffset > -1;
}
function isDirectiveHost(tNode) {
return (tNode.flags & 1 /* TNodeFlags.isDirectiveHost */) === 1 /* TNodeFlags.isDirectiveHost */;
}
function isComponentDef(def) {
return !!def.template;
}
function isRootView(target) {
// Determines whether a given LView is marked as a root view.
return (target[FLAGS] & 512 /* LViewFlags.IsRoot */) !== 0;
}
function isProjectionTNode(tNode) {
return (tNode.type & 16 /* TNodeType.Projection */) === 16 /* TNodeType.Projection */;
}
function hasI18n(lView) {
return (lView[FLAGS] & 32 /* LViewFlags.HasI18n */) === 32 /* LViewFlags.HasI18n */;
}
function isDestroyed(lView) {
// Determines whether a given LView is marked as destroyed.
return (lView[FLAGS] & 256 /* LViewFlags.Destroyed */) === 256 /* LViewFlags.Destroyed */;
}
// [Assert functions do not constraint type when they are guarded by a truthy
// expression.](https://github.com/microsoft/TypeScript/issues/37295)
function assertTNodeForLView(tNode, lView) {
assertTNodeForTView(tNode, lView[TVIEW]);
}
function assertTNodeForTView(tNode, tView) {
assertTNode(tNode);
const tData = tView.data;
for (let i = HEADER_OFFSET; i < tData.length; i++) {
if (tData[i] === tNode) {
return;
}
}
throwError('This TNode does not belong to this TView.');
}
function assertTNode(tNode) {
assertDefined(tNode, 'TNode must be defined');
if (!(tNode && typeof tNode === 'object' && tNode.hasOwnProperty('directiveStylingLast'))) {
throwError('Not of type TNode, got: ' + tNode);
}
}
function assertTIcu(tIcu) {
assertDefined(tIcu, 'Expected TIcu to be defined');
if (!(typeof tIcu.currentCaseLViewIndex === 'number')) {
throwError('Object is not of TIcu type.');
}
}
function assertComponentType(actual, msg = "Type passed in is not ComponentType, it does not have 'ɵcmp' property.") {
if (!getComponentDef(actual)) {
throwError(msg);
}
}
function assertNgModuleType(actual, msg = "Type passed in is not NgModuleType, it does not have 'ɵmod' property.") {
if (!getNgModuleDef(actual)) {
throwError(msg);
}
}
function assertHasParent(tNode) {
assertDefined(tNode, 'currentTNode should exist!');
assertDefined(tNode.parent, 'currentTNode should have a parent');
}
function assertLContainer(value) {
assertDefined(value, 'LContainer must be defined');
assertEqual(isLContainer(value), true, 'Expecting LContainer');
}
function assertLViewOrUndefined(value) {
value && assertEqual(isLView(value), true, 'Expecting LView or undefined or null');
}
function assertLView(value) {
assertDefined(value, 'LView must be defined');
assertEqual(isLView(value), true, 'Expecting LView');
}
function assertFirstCreatePass(tView, errMessage) {
assertEqual(tView.firstCreatePass, true, errMessage || 'Should only be called in first create pass.');
}
function assertFirstUpdatePass(tView, errMessage) {
assertEqual(tView.firstUpdatePass, true, 'Should only be called in first update pass.');
}
/**
* This is a basic sanity check that an object is probably a directive def. DirectiveDef is
* an interface, so we can't do a direct instanceof check.
*/
function assertDirectiveDef(obj) {
if (obj.type === undefined || obj.selectors == undefined || obj.inputs === undefined) {
throwError(`Expected a DirectiveDef/ComponentDef and this object does not seem to have the expected shape.`);
}
}
function assertIndexInDeclRange(tView, index) {
assertBetween(HEADER_OFFSET, tView.bindingStartIndex, index);
}
function assertIndexInExpandoRange(lView, index) {
const tView = lView[1];
assertBetween(tView.expandoStartIndex, lView.length, index);
}
function assertBetween(lower, upper, index) {
if (!(lower <= index && index < upper)) {
throwError(`Index out of range (expecting ${lower} <= ${index} < ${upper})`);
}
}
function assertProjectionSlots(lView, errMessage) {
assertDefined(lView[DECLARATION_COMPONENT_VIEW], 'Component views should exist.');
assertDefined(lView[DECLARATION_COMPONENT_VIEW][T_HOST].projection, 'Components with projection nodes () must have projection slots defined.');
}
function assertParentView(lView, errMessage) {
assertDefined(lView, "Component views should always have a parent view (component's host view)");
}
/**
* This is a basic sanity check that the `injectorIndex` seems to point to what looks like a
* NodeInjector data structure.
*
* @param lView `LView` which should be checked.
* @param injectorIndex index into the `LView` where the `NodeInjector` is expected.
*/
function assertNodeInjector(lView, injectorIndex) {
assertIndexInExpandoRange(lView, injectorIndex);
assertIndexInExpandoRange(lView, injectorIndex + 8 /* NodeInjectorOffset.PARENT */);
assertNumber(lView[injectorIndex + 0], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 1], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 2], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 3], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 4], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 5], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 6], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 7], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */], 'injectorIndex should point to parent injector');
}
const SVG_NAMESPACE = 'svg';
const MATH_ML_NAMESPACE = 'math';
/**
* For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`)
* in same location in `LView`. This is because we don't want to pre-allocate space for it
* because the storage is sparse. This file contains utilities for dealing with such data types.
*
* How do we know what is stored at a given location in `LView`.
* - `Array.isArray(value) === false` => `RNode` (The normal storage value)
* - `Array.isArray(value) === true` => then the `value[0]` represents the wrapped value.
* - `typeof value[TYPE] === 'object'` => `LView`
* - This happens when we have a component at a given location
* - `typeof value[TYPE] === true` => `LContainer`
* - This happens when we have `LContainer` binding at a given location.
*
*
* NOTE: it is assumed that `Array.isArray` and `typeof` operations are very efficient.
*/
/**
* Returns `RNode`.
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
function unwrapRNode(value) {
while (Array.isArray(value)) {
value = value[HOST];
}
return value;
}
/**
* Returns `LView` or `null` if not found.
* @param value wrapped value of `RNode`, `LView`, `LContainer`
*/
function unwrapLView(value) {
while (Array.isArray(value)) {
// This check is same as `isLView()` but we don't call at as we don't want to call
// `Array.isArray()` twice and give JITer more work for inlining.
if (typeof value[TYPE] === 'object')
return value;
value = value[HOST];
}
return null;
}
/**
* Retrieves an element value from the provided `viewData`, by unwrapping
* from any containers, component views, or style contexts.
*/
function getNativeByIndex(index, lView) {
ngDevMode && assertIndexInRange(lView, index);
ngDevMode && assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Expected to be past HEADER_OFFSET');
return unwrapRNode(lView[index]);
}
/**
* Retrieve an `RNode` for a given `TNode` and `LView`.
*
* This function guarantees in dev mode to retrieve a non-null `RNode`.
*
* @param tNode
* @param lView
*/
function getNativeByTNode(tNode, lView) {
ngDevMode && assertTNodeForLView(tNode, lView);
ngDevMode && assertIndexInRange(lView, tNode.index);
const node = unwrapRNode(lView[tNode.index]);
return node;
}
/**
* Retrieve an `RNode` or `null` for a given `TNode` and `LView`.
*
* Some `TNode`s don't have associated `RNode`s. For example `Projection`
*
* @param tNode
* @param lView
*/
function getNativeByTNodeOrNull(tNode, lView) {
const index = tNode === null ? -1 : tNode.index;
if (index !== -1) {
ngDevMode && assertTNodeForLView(tNode, lView);
const node = unwrapRNode(lView[index]);
return node;
}
return null;
}
// fixme(misko): The return Type should be `TNode|null`
function getTNode(tView, index) {
ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode');
ngDevMode && assertLessThan(index, tView.data.length, 'wrong index for TNode');
const tNode = tView.data[index];
ngDevMode && tNode !== null && assertTNode(tNode);
return tNode;
}
/** Retrieves a value from any `LView` or `TData`. */
function load(view, index) {
ngDevMode && assertIndexInRange(view, index);
return view[index];
}
/** Store a value in the `data` at a given `index`. */
function store(tView, lView, index, value) {
// We don't store any static data for local variables, so the first time
// we see the template, we should store as null to avoid a sparse array
if (index >= tView.data.length) {
tView.data[index] = null;
tView.blueprint[index] = null;
}
lView[index] = value;
}
function getComponentLViewByIndex(nodeIndex, hostView) {
// Could be an LView or an LContainer. If LContainer, unwrap to find LView.
ngDevMode && assertIndexInRange(hostView, nodeIndex);
const slotValue = hostView[nodeIndex];
const lView = isLView(slotValue) ? slotValue : slotValue[HOST];
return lView;
}
/** Checks whether a given view is in creation mode */
function isCreationMode(view) {
return (view[FLAGS] & 4 /* LViewFlags.CreationMode */) === 4 /* LViewFlags.CreationMode */;
}
/**
* Returns a boolean for whether the view is attached to the change detection tree.
*
* Note: This determines whether a view should be checked, not whether it's inserted
* into a container. For that, you'll want `viewAttachedToContainer` below.
*/
function viewAttachedToChangeDetector(view) {
return (view[FLAGS] & 128 /* LViewFlags.Attached */) === 128 /* LViewFlags.Attached */;
}
/** Returns a boolean for whether the view is attached to a container. */
function viewAttachedToContainer(view) {
return isLContainer(view[PARENT]);
}
function getConstant(consts, index) {
if (index === null || index === undefined)
return null;
ngDevMode && assertIndexInRange(consts, index);
return consts[index];
}
/**
* Resets the pre-order hook flags of the view.
* @param lView the LView on which the flags are reset
*/
function resetPreOrderHookFlags(lView) {
lView[PREORDER_HOOK_FLAGS] = 0;
}
/**
* Adds the `RefreshView` flag from the lView and updates HAS_CHILD_VIEWS_TO_REFRESH flag of
* parents.
*/
function markViewForRefresh(lView) {
if (lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) {
return;
}
lView[FLAGS] |= 1024 /* LViewFlags.RefreshView */;
if (viewAttachedToChangeDetector(lView)) {
markAncestorsForTraversal(lView);
}
}
/**
* Walks up the LView hierarchy.
* @param nestingLevel Number of times to walk up in hierarchy.
* @param currentView View from which to start the lookup.
*/
function walkUpViews(nestingLevel, currentView) {
while (nestingLevel > 0) {
ngDevMode &&
assertDefined(currentView[DECLARATION_VIEW], 'Declaration view should be defined if nesting level is greater than 0.');
currentView = currentView[DECLARATION_VIEW];
nestingLevel--;
}
return currentView;
}
function requiresRefreshOrTraversal(lView) {
return !!(lView[FLAGS] & (1024 /* LViewFlags.RefreshView */ | 8192 /* LViewFlags.HasChildViewsToRefresh */) ||
lView[REACTIVE_TEMPLATE_CONSUMER]?.dirty);
}
/**
* Updates the `HasChildViewsToRefresh` flag on the parents of the `LView` as well as the
* parents above.
*/
function updateAncestorTraversalFlagsOnAttach(lView) {
lView[ENVIRONMENT].changeDetectionScheduler?.notify(8 /* NotificationSource.ViewAttached */);
if (lView[FLAGS] & 64 /* LViewFlags.Dirty */) {
lView[FLAGS] |= 1024 /* LViewFlags.RefreshView */;
}
if (requiresRefreshOrTraversal(lView)) {
markAncestorsForTraversal(lView);
}
}
/**
* Ensures views above the given `lView` are traversed during change detection even when they are
* not dirty.
*
* This is done by setting the `HAS_CHILD_VIEWS_TO_REFRESH` flag up to the root, stopping when the
* flag is already `true` or the `lView` is detached.
*/
function markAncestorsForTraversal(lView) {
lView[ENVIRONMENT].changeDetectionScheduler?.notify(0 /* NotificationSource.MarkAncestorsForTraversal */);
let parent = getLViewParent(lView);
while (parent !== null) {
// We stop adding markers to the ancestors once we reach one that already has the marker. This
// is to avoid needlessly traversing all the way to the root when the marker already exists.
if (parent[FLAGS] & 8192 /* LViewFlags.HasChildViewsToRefresh */) {
break;
}
parent[FLAGS] |= 8192 /* LViewFlags.HasChildViewsToRefresh */;
if (!viewAttachedToChangeDetector(parent)) {
break;
}
parent = getLViewParent(parent);
}
}
/**
* Stores a LView-specific destroy callback.
*/
function storeLViewOnDestroy(lView, onDestroyCallback) {
if (isDestroyed(lView)) {
throw new RuntimeError(911 /* RuntimeErrorCode.VIEW_ALREADY_DESTROYED */, ngDevMode && 'View has already been destroyed.');
}
if (lView[ON_DESTROY_HOOKS] === null) {
lView[ON_DESTROY_HOOKS] = [];
}
lView[ON_DESTROY_HOOKS].push(onDestroyCallback);
}
/**
* Removes previously registered LView-specific destroy callback.
*/
function removeLViewOnDestroy(lView, onDestroyCallback) {
if (lView[ON_DESTROY_HOOKS] === null)
return;
const destroyCBIdx = lView[ON_DESTROY_HOOKS].indexOf(onDestroyCallback);
if (destroyCBIdx !== -1) {
lView[ON_DESTROY_HOOKS].splice(destroyCBIdx, 1);
}
}
/**
* Gets the parent LView of the passed LView, if the PARENT is an LContainer, will get the parent of
* that LContainer, which is an LView
* @param lView the lView whose parent to get
*/
function getLViewParent(lView) {
ngDevMode && assertLView(lView);
const parent = lView[PARENT];
return isLContainer(parent) ? parent[PARENT] : parent;
}
function getOrCreateLViewCleanup(view) {
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
return (view[CLEANUP] ??= []);
}
function getOrCreateTViewCleanup(tView) {
return (tView.cleanup ??= []);
}
/**
* Saves context for this cleanup function in LView.cleanupInstances.
*
* On the first template pass, saves in TView:
* - Cleanup function
* - Index of context we just saved in LView.cleanupInstances
*/
function storeCleanupWithContext(tView, lView, context, cleanupFn) {
const lCleanup = getOrCreateLViewCleanup(lView);
// Historically the `storeCleanupWithContext` was used to register both framework-level and
// user-defined cleanup callbacks, but over time those two types of cleanups were separated.
// This dev mode checks assures that user-level cleanup callbacks are _not_ stored in data
// structures reserved for framework-specific hooks.
ngDevMode &&
assertDefined(context, 'Cleanup context is mandatory when registering framework-level destroy hooks');
lCleanup.push(context);
if (tView.firstCreatePass) {
getOrCreateTViewCleanup(tView).push(cleanupFn, lCleanup.length - 1);
}
else {
// Make sure that no new framework-level cleanup functions are registered after the first
// template pass is done (and TView data structures are meant to fully constructed).
if (ngDevMode) {
Object.freeze(getOrCreateTViewCleanup(tView));
}
}
}
const instructionState = {
lFrame: createLFrame(null),
bindingsEnabled: true,
skipHydrationRootTNode: null,
};
var CheckNoChangesMode;
(function (CheckNoChangesMode) {
CheckNoChangesMode[CheckNoChangesMode["Off"] = 0] = "Off";
CheckNoChangesMode[CheckNoChangesMode["Exhaustive"] = 1] = "Exhaustive";
CheckNoChangesMode[CheckNoChangesMode["OnlyDirtyViews"] = 2] = "OnlyDirtyViews";
})(CheckNoChangesMode || (CheckNoChangesMode = {}));
/**
* In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error.
*
* Necessary to support ChangeDetectorRef.checkNoChanges().
*
* The `checkNoChanges` function is invoked only in ngDevMode=true and verifies that no unintended
* changes exist in the change detector or its children.
*/
let _checkNoChangesMode = 0; /* CheckNoChangesMode.Off */
/**
* Flag used to indicate that we are in the middle running change detection on a view
*
* @see detectChangesInViewWhileDirty
*/
let _isRefreshingViews = false;
function getElementDepthCount() {
return instructionState.lFrame.elementDepthCount;
}
function increaseElementDepthCount() {
instructionState.lFrame.elementDepthCount++;
}
function decreaseElementDepthCount() {
instructionState.lFrame.elementDepthCount--;
}
function getBindingsEnabled() {
return instructionState.bindingsEnabled;
}
/**
* Returns true if currently inside a skip hydration block.
* @returns boolean
*/
function isInSkipHydrationBlock() {
return instructionState.skipHydrationRootTNode !== null;
}
/**
* Returns true if this is the root TNode of the skip hydration block.
* @param tNode the current TNode
* @returns boolean
*/
function isSkipHydrationRootTNode(tNode) {
return instructionState.skipHydrationRootTNode === tNode;
}
/**
* Enables directive matching on elements.
*
* * Example:
* ```html
*
* Should match component / directive.
*
*
*
*
* Should not match component / directive because we are in ngNonBindable.
*
*
*
* ```
*
* @codeGenApi
*/
function ɵɵenableBindings() {
instructionState.bindingsEnabled = true;
}
/**
* Sets a flag to specify that the TNode is in a skip hydration block.
* @param tNode the current TNode
*/
function enterSkipHydrationBlock(tNode) {
instructionState.skipHydrationRootTNode = tNode;
}
/**
* Disables directive matching on element.
*
* * Example:
* ```html
*
* Should match component / directive.
*
*
*
*
* Should not match component / directive because we are in ngNonBindable.
*
*
*
* ```
*
* @codeGenApi
*/
function ɵɵdisableBindings() {
instructionState.bindingsEnabled = false;
}
/**
* Clears the root skip hydration node when leaving a skip hydration block.
*/
function leaveSkipHydrationBlock() {
instructionState.skipHydrationRootTNode = null;
}
/**
* Return the current `LView`.
*/
function getLView() {
return instructionState.lFrame.lView;
}
/**
* Return the current `TView`.
*/
function getTView() {
return instructionState.lFrame.tView;
}
/**
* Restores `contextViewData` to the given OpaqueViewState instance.
*
* Used in conjunction with the getCurrentView() instruction to save a snapshot
* of the current view and restore it when listeners are invoked. This allows
* walking the declaration view tree in listeners to get vars from parent views.
*
* @param viewToRestore The OpaqueViewState instance to restore.
* @returns Context of the restored OpaqueViewState instance.
*
* @codeGenApi
*/
function ɵɵrestoreView(viewToRestore) {
instructionState.lFrame.contextLView = viewToRestore;
return viewToRestore[CONTEXT];
}
/**
* Clears the view set in `ɵɵrestoreView` from memory. Returns the passed in
* value so that it can be used as a return value of an instruction.
*
* @codeGenApi
*/
function ɵɵresetView(value) {
instructionState.lFrame.contextLView = null;
return value;
}
function getCurrentTNode() {
let currentTNode = getCurrentTNodePlaceholderOk();
while (currentTNode !== null && currentTNode.type === 64 /* TNodeType.Placeholder */) {
currentTNode = currentTNode.parent;
}
return currentTNode;
}
function getCurrentTNodePlaceholderOk() {
return instructionState.lFrame.currentTNode;
}
function getCurrentParentTNode() {
const lFrame = instructionState.lFrame;
const currentTNode = lFrame.currentTNode;
return lFrame.isParent ? currentTNode : currentTNode.parent;
}
function setCurrentTNode(tNode, isParent) {
ngDevMode && tNode && assertTNodeForTView(tNode, instructionState.lFrame.tView);
const lFrame = instructionState.lFrame;
lFrame.currentTNode = tNode;
lFrame.isParent = isParent;
}
function isCurrentTNodeParent() {
return instructionState.lFrame.isParent;
}
function setCurrentTNodeAsNotParent() {
instructionState.lFrame.isParent = false;
}
function getContextLView() {
const contextLView = instructionState.lFrame.contextLView;
ngDevMode && assertDefined(contextLView, 'contextLView must be defined.');
return contextLView;
}
function isInCheckNoChangesMode() {
!ngDevMode && throwError('Must never be called in production mode');
return _checkNoChangesMode !== CheckNoChangesMode.Off;
}
function isExhaustiveCheckNoChanges() {
!ngDevMode && throwError('Must never be called in production mode');
return _checkNoChangesMode === CheckNoChangesMode.Exhaustive;
}
function setIsInCheckNoChangesMode(mode) {
!ngDevMode && throwError('Must never be called in production mode');
_checkNoChangesMode = mode;
}
function isRefreshingViews() {
return _isRefreshingViews;
}
function setIsRefreshingViews(mode) {
const prev = _isRefreshingViews;
_isRefreshingViews = mode;
return prev;
}
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
function getBindingRoot() {
const lFrame = instructionState.lFrame;
let index = lFrame.bindingRootIndex;
if (index === -1) {
index = lFrame.bindingRootIndex = lFrame.tView.bindingStartIndex;
}
return index;
}
function getBindingIndex() {
return instructionState.lFrame.bindingIndex;
}
function setBindingIndex(value) {
return (instructionState.lFrame.bindingIndex = value);
}
function nextBindingIndex() {
return instructionState.lFrame.bindingIndex++;
}
function incrementBindingIndex(count) {
const lFrame = instructionState.lFrame;
const index = lFrame.bindingIndex;
lFrame.bindingIndex = lFrame.bindingIndex + count;
return index;
}
function isInI18nBlock() {
return instructionState.lFrame.inI18n;
}
function setInI18nBlock(isInI18nBlock) {
instructionState.lFrame.inI18n = isInI18nBlock;
}
/**
* Set a new binding root index so that host template functions can execute.
*
* Bindings inside the host template are 0 index. But because we don't know ahead of time
* how many host bindings we have we can't pre-compute them. For this reason they are all
* 0 index and we just shift the root so that they match next available location in the LView.
*
* @param bindingRootIndex Root index for `hostBindings`
* @param currentDirectiveIndex `TData[currentDirectiveIndex]` will point to the current directive
* whose `hostBindings` are being processed.
*/
function setBindingRootForHostBindings(bindingRootIndex, currentDirectiveIndex) {
const lFrame = instructionState.lFrame;
lFrame.bindingIndex = lFrame.bindingRootIndex = bindingRootIndex;
setCurrentDirectiveIndex(currentDirectiveIndex);
}
/**
* When host binding is executing this points to the directive index.
* `TView.data[getCurrentDirectiveIndex()]` is `DirectiveDef`
* `LView[getCurrentDirectiveIndex()]` is directive instance.
*/
function getCurrentDirectiveIndex() {
return instructionState.lFrame.currentDirectiveIndex;
}
/**
* Sets an index of a directive whose `hostBindings` are being processed.
*
* @param currentDirectiveIndex `TData` index where current directive instance can be found.
*/
function setCurrentDirectiveIndex(currentDirectiveIndex) {
instructionState.lFrame.currentDirectiveIndex = currentDirectiveIndex;
}
/**
* Retrieve the current `DirectiveDef` which is active when `hostBindings` instruction is being
* executed.
*
* @param tData Current `TData` where the `DirectiveDef` will be looked up at.
*/
function getCurrentDirectiveDef(tData) {
const currentDirectiveIndex = instructionState.lFrame.currentDirectiveIndex;
return currentDirectiveIndex === -1 ? null : tData[currentDirectiveIndex];
}
function getCurrentQueryIndex() {
return instructionState.lFrame.currentQueryIndex;
}
function setCurrentQueryIndex(value) {
instructionState.lFrame.currentQueryIndex = value;
}
/**
* Returns a `TNode` of the location where the current `LView` is declared at.
*
* @param lView an `LView` that we want to find parent `TNode` for.
*/
function getDeclarationTNode(lView) {
const tView = lView[TVIEW];
// Return the declaration parent for embedded views
if (tView.type === 2 /* TViewType.Embedded */) {
ngDevMode && assertDefined(tView.declTNode, 'Embedded TNodes should have declaration parents.');
return tView.declTNode;
}
// Components don't have `TView.declTNode` because each instance of component could be
// inserted in different location, hence `TView.declTNode` is meaningless.
// Falling back to `T_HOST` in case we cross component boundary.
if (tView.type === 1 /* TViewType.Component */) {
return lView[T_HOST];
}
// Remaining TNode type is `TViewType.Root` which doesn't have a parent TNode.
return null;
}
/**
* This is a light weight version of the `enterView` which is needed by the DI system.
*
* @param lView `LView` location of the DI context.
* @param tNode `TNode` for DI context
* @param flags DI context flags. if `SkipSelf` flag is set than we walk up the declaration
* tree from `tNode` until we find parent declared `TElementNode`.
* @returns `true` if we have successfully entered DI associated with `tNode` (or with declared
* `TNode` if `flags` has `SkipSelf`). Failing to enter DI implies that no associated
* `NodeInjector` can be found and we should instead use `ModuleInjector`.
* - If `true` than this call must be fallowed by `leaveDI`
* - If `false` than this call failed and we should NOT call `leaveDI`
*/
function enterDI(lView, tNode, flags) {
ngDevMode && assertLViewOrUndefined(lView);
if (flags & 4 /* InternalInjectFlags.SkipSelf */) {
ngDevMode && assertTNodeForTView(tNode, lView[TVIEW]);
let parentTNode = tNode;
let parentLView = lView;
while (true) {
ngDevMode && assertDefined(parentTNode, 'Parent TNode should be defined');
parentTNode = parentTNode.parent;
if (parentTNode === null && !(flags & 1 /* InternalInjectFlags.Host */)) {
parentTNode = getDeclarationTNode(parentLView);
if (parentTNode === null)
break;
// In this case, a parent exists and is definitely an element. So it will definitely
// have an existing lView as the declaration view, which is why we can assume it's defined.
ngDevMode && assertDefined(parentLView, 'Parent LView should be defined');
parentLView = parentLView[DECLARATION_VIEW];
// In Ivy there are Comment nodes that correspond to ngIf and NgFor embedded directives
// We want to skip those and look only at Elements and ElementContainers to ensure
// we're looking at true parent nodes, and not content or other types.
if (parentTNode.type & (2 /* TNodeType.Element */ | 8 /* TNodeType.ElementContainer */)) {
break;
}
}
else {
break;
}
}
if (parentTNode === null) {
// If we failed to find a parent TNode this means that we should use module injector.
return false;
}
else {
tNode = parentTNode;
lView = parentLView;
}
}
ngDevMode && assertTNodeForLView(tNode, lView);
const lFrame = (instructionState.lFrame = allocLFrame());
lFrame.currentTNode = tNode;
lFrame.lView = lView;
return true;
}
/**
* Swap the current lView with a new lView.
*
* For performance reasons we store the lView in the top level of the module.
* This way we minimize the number of properties to read. Whenever a new view
* is entered we have to store the lView for later, and when the view is
* exited the state has to be restored
*
* @param newView New lView to become active
* @returns the previously active lView;
*/
function enterView(newView) {
ngDevMode && assertNotEqual(newView[0], newView[1], '????');
ngDevMode && assertLViewOrUndefined(newView);
const newLFrame = allocLFrame();
if (ngDevMode) {
assertEqual(newLFrame.isParent, true, 'Expected clean LFrame');
assertEqual(newLFrame.lView, null, 'Expected clean LFrame');
assertEqual(newLFrame.tView, null, 'Expected clean LFrame');
assertEqual(newLFrame.selectedIndex, -1, 'Expected clean LFrame');
assertEqual(newLFrame.elementDepthCount, 0, 'Expected clean LFrame');
assertEqual(newLFrame.currentDirectiveIndex, -1, 'Expected clean LFrame');
assertEqual(newLFrame.currentNamespace, null, 'Expected clean LFrame');
assertEqual(newLFrame.bindingRootIndex, -1, 'Expected clean LFrame');
assertEqual(newLFrame.currentQueryIndex, 0, 'Expected clean LFrame');
}
const tView = newView[TVIEW];
instructionState.lFrame = newLFrame;
ngDevMode && tView.firstChild && assertTNodeForTView(tView.firstChild, tView);
newLFrame.currentTNode = tView.firstChild;
newLFrame.lView = newView;
newLFrame.tView = tView;
newLFrame.contextLView = newView;
newLFrame.bindingIndex = tView.bindingStartIndex;
newLFrame.inI18n = false;
}
/**
* Allocates next free LFrame. This function tries to reuse the `LFrame`s to lower memory pressure.
*/
function allocLFrame() {
const currentLFrame = instructionState.lFrame;
const childLFrame = currentLFrame === null ? null : currentLFrame.child;
const newLFrame = childLFrame === null ? createLFrame(currentLFrame) : childLFrame;
return newLFrame;
}
function createLFrame(parent) {
const lFrame = {
currentTNode: null,
isParent: true,
lView: null,
tView: null,
selectedIndex: -1,
contextLView: null,
elementDepthCount: 0,
currentNamespace: null,
currentDirectiveIndex: -1,
bindingRootIndex: -1,
bindingIndex: -1,
currentQueryIndex: 0,
parent: parent,
child: null,
inI18n: false,
};
parent !== null && (parent.child = lFrame); // link the new LFrame for reuse.
return lFrame;
}
/**
* A lightweight version of leave which is used with DI.
*
* This function only resets `currentTNode` and `LView` as those are the only properties
* used with DI (`enterDI()`).
*
* NOTE: This function is reexported as `leaveDI`. However `leaveDI` has return type of `void` where
* as `leaveViewLight` has `LFrame`. This is so that `leaveViewLight` can be used in `leaveView`.
*/
function leaveViewLight() {
const oldLFrame = instructionState.lFrame;
instructionState.lFrame = oldLFrame.parent;
oldLFrame.currentTNode = null;
oldLFrame.lView = null;
return oldLFrame;
}
/**
* This is a lightweight version of the `leaveView` which is needed by the DI system.
*
* NOTE: this function is an alias so that we can change the type of the function to have `void`
* return type.
*/
const leaveDI = leaveViewLight;
/**
* Leave the current `LView`
*
* This pops the `LFrame` with the associated `LView` from the stack.
*
* IMPORTANT: We must zero out the `LFrame` values here otherwise they will be retained. This is
* because for performance reasons we don't release `LFrame` but rather keep it for next use.
*/
function leaveView() {
const oldLFrame = leaveViewLight();
oldLFrame.isParent = true;
oldLFrame.tView = null;
oldLFrame.selectedIndex = -1;
oldLFrame.contextLView = null;
oldLFrame.elementDepthCount = 0;
oldLFrame.currentDirectiveIndex = -1;
oldLFrame.currentNamespace = null;
oldLFrame.bindingRootIndex = -1;
oldLFrame.bindingIndex = -1;
oldLFrame.currentQueryIndex = 0;
}
function nextContextImpl(level) {
const contextLView = (instructionState.lFrame.contextLView = walkUpViews(level, instructionState.lFrame.contextLView));
return contextLView[CONTEXT];
}
/**
* Gets the currently selected element index.
*
* Used with {@link property} instruction (and more in the future) to identify the index in the
* current `LView` to act on.
*/
function getSelectedIndex() {
return instructionState.lFrame.selectedIndex;
}
/**
* Sets the most recent index passed to {@link select}
*
* Used with {@link property} instruction (and more in the future) to identify the index in the
* current `LView` to act on.
*
* (Note that if an "exit function" was set earlier (via `setElementExitFn()`) then that will be
* run if and when the provided `index` value is different from the current selected index value.)
*/
function setSelectedIndex(index) {
ngDevMode &&
index !== -1 &&
assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Index must be past HEADER_OFFSET (or -1).');
ngDevMode &&
assertLessThan(index, instructionState.lFrame.lView.length, "Can't set index passed end of LView");
instructionState.lFrame.selectedIndex = index;
}
/**
* Gets the `tNode` that represents currently selected element.
*/
function getSelectedTNode() {
const lFrame = instructionState.lFrame;
return getTNode(lFrame.tView, lFrame.selectedIndex);
}
/**
* Sets the namespace used to create elements to `'http://www.w3.org/2000/svg'` in global state.
*
* @codeGenApi
*/
function ɵɵnamespaceSVG() {
instructionState.lFrame.currentNamespace = SVG_NAMESPACE;
}
/**
* Sets the namespace used to create elements to `'http://www.w3.org/1998/MathML/'` in global state.
*
* @codeGenApi
*/
function ɵɵnamespaceMathML() {
instructionState.lFrame.currentNamespace = MATH_ML_NAMESPACE;
}
/**
* Sets the namespace used to create elements to `null`, which forces element creation to use
* `createElement` rather than `createElementNS`.
*
* @codeGenApi
*/
function ɵɵnamespaceHTML() {
namespaceHTMLInternal();
}
/**
* Sets the namespace used to create elements to `null`, which forces element creation to use
* `createElement` rather than `createElementNS`.
*/
function namespaceHTMLInternal() {
instructionState.lFrame.currentNamespace = null;
}
function getNamespace() {
return instructionState.lFrame.currentNamespace;
}
let _wasLastNodeCreated = true;
/**
* Retrieves a global flag that indicates whether the most recent DOM node
* was created or hydrated.
*/
function wasLastNodeCreated() {
return _wasLastNodeCreated;
}
/**
* Sets a global flag to indicate whether the most recent DOM node
* was created or hydrated.
*/
function lastNodeWasCreated(flag) {
_wasLastNodeCreated = flag;
}
/**
* Create a new `Injector` which is configured using a `defType` of `InjectorType`s.
*/
function createInjector(defType, parent = null, additionalProviders = null, name) {
const injector = createInjectorWithoutInjectorInstances(defType, parent, additionalProviders, name);
injector.resolveInjectorInitializers();
return injector;
}
/**
* Creates a new injector without eagerly resolving its injector types. Can be used in places
* where resolving the injector types immediately can lead to an infinite loop. The injector types
* should be resolved at a later point by calling `_resolveInjectorDefTypes`.
*/
function createInjectorWithoutInjectorInstances(defType, parent = null, additionalProviders = null, name, scopes = new Set()) {
const providers = [additionalProviders || EMPTY_ARRAY, importProvidersFrom(defType)];
name = name || (typeof defType === 'object' ? undefined : stringify(defType));
return new R3Injector(providers, parent || getNullInjector(), name || null, scopes);
}
/**
* Concrete injectors implement this interface. Injectors are configured
* with [providers](guide/di/dependency-injection-providers) that associate
* dependencies of various types with [injection tokens](guide/di/dependency-injection-providers).
*
* @see [DI Providers](guide/di/dependency-injection-providers).
* @see {@link StaticProvider}
*
* @usageNotes
*
* The following example creates a service injector instance.
*
* {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
*
* ### Usage example
*
* {@example core/di/ts/injector_spec.ts region='Injector'}
*
* `Injector` returns itself when given `Injector` as a token:
*
* {@example core/di/ts/injector_spec.ts region='injectInjector'}
*
* @publicApi
*/
class Injector {
static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND;
static NULL = new NullInjector();
static create(options, parent) {
if (Array.isArray(options)) {
return createInjector({ name: '' }, parent, options, '');
}
else {
const name = options.name ?? '';
return createInjector({ name }, options.parent, options.providers, name);
}
}
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
token: Injector,
providedIn: 'any',
factory: () => ɵɵinject(INJECTOR$1),
});
/**
* @internal
* @nocollapse
*/
static __NG_ELEMENT_ID__ = -1 /* InjectorMarkers.Injector */;
}
/**
* A DI Token representing the main rendering context.
* In a browser and SSR this is the DOM Document.
* When using SSR, that document is created by [Domino](https://github.com/angular/domino).
*
* @publicApi
*/
const DOCUMENT = new InjectionToken(ngDevMode ? 'DocumentToken' : '');
/**
* `DestroyRef` lets you set callbacks to run for any cleanup or destruction behavior.
* The scope of this destruction depends on where `DestroyRef` is injected. If `DestroyRef`
* is injected in a component or directive, the callbacks run when that component or
* directive is destroyed. Otherwise the callbacks run when a corresponding injector is destroyed.
*
* @publicApi
*/
class DestroyRef {
/**
* @internal
* @nocollapse
*/
static __NG_ELEMENT_ID__ = injectDestroyRef;
/**
* @internal
* @nocollapse
*/
static __NG_ENV_ID__ = (injector) => injector;
}
class NodeInjectorDestroyRef extends DestroyRef {
_lView;
constructor(_lView) {
super();
this._lView = _lView;
}
onDestroy(callback) {
const lView = this._lView;
storeLViewOnDestroy(lView, callback);
return () => removeLViewOnDestroy(lView, callback);
}
}
function injectDestroyRef() {
return new NodeInjectorDestroyRef(getLView());
}
/**
* Provides a hook for centralized exception handling.
*
* The default implementation of `ErrorHandler` prints error messages to the `console`. To
* intercept error handling, write a custom exception handler that replaces this default as
* appropriate for your app.
*
* @usageNotes
* ### Example
*
* ```ts
* class MyErrorHandler implements ErrorHandler {
* handleError(error) {
* // do something with the exception
* }
* }
*
* // Provide in standalone apps
* bootstrapApplication(AppComponent, {
* providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
* })
*
* // Provide in module-based apps
* @NgModule({
* providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
* })
* class MyModule {}
* ```
*
* @publicApi
*/
class ErrorHandler {
/**
* @internal
*/
_console = console;
handleError(error) {
this._console.error('ERROR', error);
}
}
/**
* `InjectionToken` used to configure how to call the `ErrorHandler`.
*/
const INTERNAL_APPLICATION_ERROR_HANDLER = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'internal error handler' : '', {
providedIn: 'root',
factory: () => {
// The user's error handler may depend on things that create a circular dependency
// so we inject it lazily.
const injector = inject(EnvironmentInjector);
let userErrorHandler;
return (e) => {
userErrorHandler ??= injector.get(ErrorHandler);
userErrorHandler.handleError(e);
};
},
});
const errorHandlerEnvironmentInitializer = {
provide: ENVIRONMENT_INITIALIZER,
useValue: () => void inject(ErrorHandler),
multi: true,
};
const globalErrorListeners = new InjectionToken(ngDevMode ? 'GlobalErrorListeners' : '', {
providedIn: 'root',
factory: () => {
if (typeof ngServerMode !== 'undefined' && ngServerMode) {
return;
}
const window = inject(DOCUMENT).defaultView;
if (!window) {
return;
}
const errorHandler = inject(INTERNAL_APPLICATION_ERROR_HANDLER);
const rejectionListener = (e) => {
errorHandler(e.reason);
e.preventDefault();
};
const errorListener = (e) => {
errorHandler(e.error);
e.preventDefault();
};
const setupEventListeners = () => {
window.addEventListener('unhandledrejection', rejectionListener);
window.addEventListener('error', errorListener);
};
// Angular doesn't have to run change detection whenever any asynchronous tasks are invoked in
// the scope of this functionality.
if (typeof Zone !== 'undefined') {
Zone.root.run(setupEventListeners);
}
else {
setupEventListeners();
}
inject(DestroyRef).onDestroy(() => {
window.removeEventListener('error', errorListener);
window.removeEventListener('unhandledrejection', rejectionListener);
});
},
});
/**
* Provides an environment initializer which forwards unhandled errors to the ErrorHandler.
*
* The listeners added are for the window's 'unhandledrejection' and 'error' events.
*
* @publicApi
*/
function provideBrowserGlobalErrorListeners() {
return makeEnvironmentProviders([
provideEnvironmentInitializer(() => void inject(globalErrorListeners)),
]);
}
/**
* Checks if the given `value` is a reactive `Signal`.
*
* @publicApi 17.0
*/
function isSignal(value) {
return typeof value === 'function' && value[SIGNAL] !== undefined;
}
/**
* Utility function used during template type checking to extract the value from a `WritableSignal`.
* @codeGenApi
*/
function ɵunwrapWritableSignal(value) {
// Note: the function uses `WRITABLE_SIGNAL` as a brand instead of `WritableSignal`,
// because the latter incorrectly unwraps non-signal getter functions.
return null;
}
/**
* Create a `Signal` that can be set or updated directly.
*/
function signal(initialValue, options) {
const signalFn = createSignal(initialValue, options?.equal);
const node = signalFn[SIGNAL];
signalFn.set = (newValue) => signalSetFn(node, newValue);
signalFn.update = (updateFn) => signalUpdateFn(node, updateFn);
signalFn.asReadonly = signalAsReadonlyFn.bind(signalFn);
if (ngDevMode) {
signalFn.toString = () => `[Signal: ${signalFn()}]`;
node.debugName = options?.debugName;
}
return signalFn;
}
function signalAsReadonlyFn() {
const node = this[SIGNAL];
if (node.readonlyFn === undefined) {
const readonlyFn = () => this();
readonlyFn[SIGNAL] = node;
node.readonlyFn = readonlyFn;
}
return node.readonlyFn;
}
/**
* Checks if the given `value` is a writeable signal.
*/
function isWritableSignal(value) {
return isSignal(value) && typeof value.set === 'function';
}
/**
* Injectable that is notified when an `LView` is made aware of changes to application state.
*/
class ChangeDetectionScheduler {
}
/** Token used to indicate if zoneless was enabled via provideZonelessChangeDetection(). */
const ZONELESS_ENABLED = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless enabled' : '', { providedIn: 'root', factory: () => false });
/** Token used to indicate `provideZonelessChangeDetection` was used. */
const PROVIDED_ZONELESS = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'Zoneless provided' : '', { providedIn: 'root', factory: () => false });
const ZONELESS_SCHEDULER_DISABLED = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'scheduler disabled' : '');
// TODO(atscott): Remove in v19. Scheduler should be done with runOutsideAngular.
const SCHEDULE_IN_ROOT_ZONE = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'run changes outside zone in root' : '');
/**
* Asserts that the current stack frame is not within a reactive context. Useful
* to disallow certain code from running inside a reactive context (see {@link /api/core/rxjs/toSignal toSignal})
*
* @param debugFn a reference to the function making the assertion (used for the error message).
*
* @publicApi
*/
function assertNotInReactiveContext(debugFn, extraContext) {
// Taking a `Function` instead of a string name here prevents the un-minified name of the function
// from being retained in the bundle regardless of minification.
if (getActiveConsumer() !== null) {
throw new RuntimeError(-602 /* RuntimeErrorCode.ASSERTION_NOT_INSIDE_REACTIVE_CONTEXT */, ngDevMode &&
`${debugFn.name}() cannot be called from within a reactive context.${extraContext ? ` ${extraContext}` : ''}`);
}
}
class ViewContext {
view;
node;
constructor(view, node) {
this.view = view;
this.node = node;
}
/**
* @internal
* @nocollapse
*/
static __NG_ELEMENT_ID__ = injectViewContext;
}
function injectViewContext() {
return new ViewContext(getLView(), getCurrentTNode());
}
/**
* Internal implementation of the pending tasks service.
*/
class PendingTasksInternal {
taskId = 0;
pendingTasks = new Set();
destroyed = false;
pendingTask = new BehaviorSubject(false);
get hasPendingTasks() {
// Accessing the value of a closed `BehaviorSubject` throws an error.
return this.destroyed ? false : this.pendingTask.value;
}
/**
* In case the service is about to be destroyed, return a self-completing observable.
* Otherwise, return the observable that emits the current state of pending tasks.
*/
get hasPendingTasksObservable() {
if (this.destroyed) {
// Manually creating the observable pulls less symbols from RxJS than `of(false)`.
return new Observable((subscriber) => {
subscriber.next(false);
subscriber.complete();
});
}
return this.pendingTask;
}
add() {
// Emitting a value to a closed subject throws an error.
if (!this.hasPendingTasks && !this.destroyed) {
this.pendingTask.next(true);
}
const taskId = this.taskId++;
this.pendingTasks.add(taskId);
return taskId;
}
has(taskId) {
return this.pendingTasks.has(taskId);
}
remove(taskId) {
this.pendingTasks.delete(taskId);
if (this.pendingTasks.size === 0 && this.hasPendingTasks) {
this.pendingTask.next(false);
}
}
ngOnDestroy() {
this.pendingTasks.clear();
if (this.hasPendingTasks) {
this.pendingTask.next(false);
}
// We call `unsubscribe()` to release observers, as users may forget to
// unsubscribe manually when subscribing to `isStable`. We do not call
// `complete()` because it is unsafe; if someone subscribes using the `first`
// operator and the observable completes before emitting a value,
// RxJS will throw an error.
this.destroyed = true;
this.pendingTask.unsubscribe();
}
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
token: PendingTasksInternal,
providedIn: 'root',
factory: () => new PendingTasksInternal(),
});
}
/**
* Service that keeps track of pending tasks contributing to the stableness of Angular
* application. While several existing Angular services (ex.: `HttpClient`) will internally manage
* tasks influencing stability, this API gives control over stability to library and application
* developers for specific cases not covered by Angular internals.
*
* The concept of stability comes into play in several important scenarios:
* - SSR process needs to wait for the application stability before serializing and sending rendered
* HTML;
* - tests might want to delay assertions until the application becomes stable;
*
* @usageNotes
* ```ts
* const pendingTasks = inject(PendingTasks);
* const taskCleanup = pendingTasks.add();
* // do work that should block application's stability and then:
* taskCleanup();
* ```
*
* @publicApi 20.0
*/
class PendingTasks {
internalPendingTasks = inject(PendingTasksInternal);
scheduler = inject(ChangeDetectionScheduler);
errorHandler = inject(INTERNAL_APPLICATION_ERROR_HANDLER);
/**
* Adds a new task that should block application's stability.
* @returns A cleanup function that removes a task when called.
*/
add() {
const taskId = this.internalPendingTasks.add();
return () => {
if (!this.internalPendingTasks.has(taskId)) {
// This pending task has already been cleared.
return;
}
// Notifying the scheduler will hold application stability open until the next tick.
this.scheduler.notify(11 /* NotificationSource.PendingTaskRemoved */);
this.internalPendingTasks.remove(taskId);
};
}
/**
* Runs an asynchronous function and blocks the application's stability until the function completes.
*
* ```ts
* pendingTasks.run(async () => {
* const userData = await fetch('/api/user');
* this.userData.set(userData);
* });
* ```
*
* @param fn The asynchronous function to execute
* @developerPreview 19.0
*/
run(fn) {
const removeTask = this.add();
fn().catch(this.errorHandler).finally(removeTask);
}
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
token: PendingTasks,
providedIn: 'root',
factory: () => new PendingTasks(),
});
}
function noop(...args) {
// Do nothing.
}
/**
* A scheduler which manages the execution of effects.
*/
class EffectScheduler {
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
token: EffectScheduler,
providedIn: 'root',
factory: () => new ZoneAwareEffectScheduler(),
});
}
/**
* A wrapper around `ZoneAwareQueueingScheduler` that schedules flushing via the microtask queue
* when.
*/
class ZoneAwareEffectScheduler {
dirtyEffectCount = 0;
queues = new Map();
add(handle) {
this.enqueue(handle);
this.schedule(handle);
}
schedule(handle) {
if (!handle.dirty) {
return;
}
this.dirtyEffectCount++;
}
remove(handle) {
const zone = handle.zone;
const queue = this.queues.get(zone);
if (!queue.has(handle)) {
return;
}
queue.delete(handle);
if (handle.dirty) {
this.dirtyEffectCount--;
}
}
enqueue(handle) {
const zone = handle.zone;
if (!this.queues.has(zone)) {
this.queues.set(zone, new Set());
}
const queue = this.queues.get(zone);
if (queue.has(handle)) {
return;
}
queue.add(handle);
}
/**
* Run all scheduled effects.
*
* Execution order of effects within the same zone is guaranteed to be FIFO, but there is no
* ordering guarantee between effects scheduled in different zones.
*/
flush() {
while (this.dirtyEffectCount > 0) {
let ranOneEffect = false;
for (const [zone, queue] of this.queues) {
// `zone` here must be defined.
if (zone === null) {
ranOneEffect ||= this.flushQueue(queue);
}
else {
ranOneEffect ||= zone.run(() => this.flushQueue(queue));
}
}
// Safeguard against infinite looping if somehow our dirty effect count gets out of sync with
// the dirty flag across all the effects.
if (!ranOneEffect) {
this.dirtyEffectCount = 0;
}
}
}
flushQueue(queue) {
let ranOneEffect = false;
for (const handle of queue) {
if (!handle.dirty) {
continue;
}
this.dirtyEffectCount--;
ranOneEffect = true;
// TODO: what happens if this throws an error?
handle.run();
}
return ranOneEffect;
}
}
export { AFTER_RENDER_SEQUENCES_TO_ADD, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTAINER_HEADER_OFFSET, CONTEXT, ChangeDetectionScheduler, CheckNoChangesMode, DECLARATION_COMPONENT_VIEW, DECLARATION_LCONTAINER, DECLARATION_VIEW, DEHYDRATED_VIEWS, DOCUMENT, DestroyRef, EFFECTS, EFFECTS_TO_SCHEDULE, EMBEDDED_VIEW_INJECTOR, EMPTY_ARRAY, EMPTY_OBJ, ENVIRONMENT, ENVIRONMENT_INITIALIZER, EffectScheduler, EnvironmentInjector, ErrorHandler, FLAGS, HEADER_OFFSET, HOST, HYDRATION, ID, INJECTOR$1 as INJECTOR, INJECTOR as INJECTOR$1, INJECTOR_DEF_TYPES, INJECTOR_SCOPE, INTERNAL_APPLICATION_ERROR_HANDLER, InjectionToken, Injector, MATH_ML_NAMESPACE, MOVED_VIEWS, NATIVE, NEXT, NG_COMP_DEF, NG_DIR_DEF, NG_ELEMENT_ID, NG_FACTORY_DEF, NG_INJ_DEF, NG_MOD_DEF, NG_PIPE_DEF, NG_PROV_DEF, NodeInjectorDestroyRef, NullInjector, ON_DESTROY_HOOKS, PARENT, PREORDER_HOOK_FLAGS, PROVIDED_ZONELESS, PendingTasks, PendingTasksInternal, QUERIES, R3Injector, REACTIVE_TEMPLATE_CONSUMER, RENDERER, RuntimeError, SCHEDULE_IN_ROOT_ZONE, SVG_NAMESPACE, TVIEW, T_HOST, VIEW_REFS, ViewContext, XSS_SECURITY_URL, ZONELESS_ENABLED, ZONELESS_SCHEDULER_DISABLED, _global, addToArray, arrayEquals, arrayInsert2, arraySplice, assertComponentType, assertDefined, assertDirectiveDef, assertDomNode, assertElement, assertEqual, assertFirstCreatePass, assertFirstUpdatePass, assertFunction, assertGreaterThan, assertGreaterThanOrEqual, assertHasParent, assertInInjectionContext, assertIndexInDeclRange, assertIndexInExpandoRange, assertIndexInRange, assertInjectImplementationNotEqual, assertLContainer, assertLView, assertLessThan, assertNgModuleType, assertNodeInjector, assertNotDefined, assertNotEqual, assertNotInReactiveContext, assertNotReactive, assertNotSame, assertNumber, assertNumberInRange, assertOneOf, assertParentView, assertProjectionSlots, assertSame, assertString, assertTIcu, assertTNode, assertTNodeForLView, assertTNodeForTView, attachInjectFlag, concatStringsWithSpace, convertToBitFlags, createInjector, createInjectorWithoutInjectorInstances, debugStringifyTypeForError, decreaseElementDepthCount, deepForEach, defineInjectable, emitEffectCreatedEvent, emitInjectEvent, emitInjectorToCreateInstanceEvent, emitInstanceCreatedByInjectorEvent, emitProviderConfiguredEvent, enterDI, enterSkipHydrationBlock, enterView, errorHandlerEnvironmentInitializer, fillProperties, flatten, formatRuntimeError, forwardRef, getBindingIndex, getBindingRoot, getBindingsEnabled, getClosureSafeProperty, getComponentDef, getComponentLViewByIndex, getConstant, getContextLView, getCurrentDirectiveDef, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentQueryIndex, getCurrentTNode, getCurrentTNodePlaceholderOk, getDirectiveDef, getDirectiveDefOrThrow, getElementDepthCount, getFactoryDef, getInjectableDef, getInjectorDef, getLView, getLViewParent, getNamespace, getNativeByIndex, getNativeByTNode, getNativeByTNodeOrNull, getNgModuleDef, getNgModuleDefOrThrow, getNullInjector, getOrCreateLViewCleanup, getOrCreateTViewCleanup, getPipeDef, getSelectedIndex, getSelectedTNode, getTNode, getTView, hasI18n, importProvidersFrom, increaseElementDepthCount, incrementBindingIndex, initNgDevMode, inject, injectRootLimpMode, internalImportProvidersFrom, isClassProvider, isComponentDef, isComponentHost, isContentQueryHost, isCreationMode, isCurrentTNodeParent, isDestroyed, isDirectiveHost, isEnvironmentProviders, isExhaustiveCheckNoChanges, isForwardRef, isInCheckNoChangesMode, isInI18nBlock, isInInjectionContext, isInSkipHydrationBlock, isInjectable, isLContainer, isLView, isProjectionTNode, isRefreshingViews, isRootView, isSignal, isSkipHydrationRootTNode, isStandalone, isTypeProvider, isWritableSignal, keyValueArrayGet, keyValueArrayIndexOf, keyValueArraySet, lastNodeWasCreated, leaveDI, leaveSkipHydrationBlock, leaveView, load, makeEnvironmentProviders, markAncestorsForTraversal, markViewForRefresh, newArray, nextBindingIndex, nextContextImpl, noop, provideBrowserGlobalErrorListeners, provideEnvironmentInitializer, providerToFactory, removeFromArray, removeLViewOnDestroy, renderStringify, requiresRefreshOrTraversal, resetPreOrderHookFlags, resolveForwardRef, runInInjectionContext, runInInjectorProfilerContext, setBindingIndex, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setCurrentTNodeAsNotParent, setInI18nBlock, setInjectImplementation, setInjectorProfiler, setInjectorProfilerContext, setIsInCheckNoChangesMode, setIsRefreshingViews, setSelectedIndex, signal, signalAsReadonlyFn, store, storeCleanupWithContext, storeLViewOnDestroy, stringify, stringifyForError, throwCyclicDependencyError, throwError, throwProviderNotFoundError, truncateMiddle, unwrapLView, unwrapRNode, updateAncestorTraversalFlagsOnAttach, viewAttachedToChangeDetector, viewAttachedToContainer, walkProviderTree, walkUpViews, wasLastNodeCreated, ɵunwrapWritableSignal, ɵɵdefineInjectable, ɵɵdefineInjector, ɵɵdisableBindings, ɵɵenableBindings, ɵɵinject, ɵɵinvalidFactoryDep, ɵɵnamespaceHTML, ɵɵnamespaceMathML, ɵɵnamespaceSVG, ɵɵresetView, ɵɵrestoreView };
//# sourceMappingURL=root_effect_scheduler-0BxwqIgm.mjs.map