UNPKG

@angular/core

Version:

Angular - the core framework

1,341 lines (1,320 loc) 151 kB
/** * @license Angular v20.0.4 * (c) 2010-2025 Google LLC. https://angular.io/ * License: MIT */ import { isNotFound, getCurrentInjector, setCurrentInjector } from './primitives/di.mjs'; import { getActiveConsumer, SIGNAL, createSignal } from './signal-nCiHhWf6.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({ * imports: [ChildComponent], * selector: 'app-parent', * template: `<app-child [hideParent]="hideParent()"></app-child>`, * }) * export class ParentComponent { * hideParent = input.required<boolean>(); * } * * * @Component({ * imports: [forwardRef(() => ParentComponent)], * selector: 'app-child', * template: ` * @if(!hideParent() { * <app-parent/> * } * `, * }) * export class ChildComponent { * hideParent = input.required<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. * * <div class="docs-alert docs-alert-helpful"> * * **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`. * * </div> * * {@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 h