package.fesm2022.core.mjs Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Angular - the core framework
/**
* @license Angular v18.2.7
* (c) 2010-2024 Google LLC. https://angular.io/
* License: MIT
*/
import { SIGNAL_NODE as SIGNAL_NODE$1, signalSetFn as signalSetFn$1, producerAccessed as producerAccessed$1, SIGNAL as SIGNAL$1, getActiveConsumer as getActiveConsumer$1, setActiveConsumer as setActiveConsumer$1, consumerDestroy as consumerDestroy$1, REACTIVE_NODE as REACTIVE_NODE$1, consumerBeforeComputation as consumerBeforeComputation$1, consumerAfterComputation as consumerAfterComputation$1, consumerPollProducersForChange as consumerPollProducersForChange$1, createSignal as createSignal$1, signalUpdateFn as signalUpdateFn$1, createComputed as createComputed$1, setThrowInvalidWriteToSignalError as setThrowInvalidWriteToSignalError$1, createWatch as createWatch$1 } from '@angular/core/primitives/signals';
export { SIGNAL as ɵSIGNAL } from '@angular/core/primitives/signals';
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import { map, first } from 'rxjs/operators';
import { Attribute as Attribute$1, EventContract, EventContractContainer, getAppScopedQueuedEventInfos, clearAppScopedEarlyEventContract, EventDispatcher, registerDispatcher, isEarlyEventType, isCaptureEventType } from '@angular/core/primitives/event-dispatch';
/**
* 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://g.co/ng/security#xss';
/**
* Class that represents a runtime error.
* Formats and outputs the error message in a consistent way.
*
* Example:
* ```
* 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 {
constructor(code, message) {
super(formatRuntimeError(code, message));
this.code = code;
}
}
/**
* Called to format a runtime error.
* See additional info on the `message` argument type in the `RuntimeError` class description.
*/
function formatRuntimeError(code, message) {
// 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.
const fullCode = `NG0${Math.abs(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 REQUIRED_UNSET_VALUE = /* @__PURE__ */ Symbol('InputSignalNode#UNSET');
// Note: Using an IIFE here to ensure that the spread assignment is not considered
// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
const INPUT_SIGNAL_NODE = /* @__PURE__ */ (() => {
return {
...SIGNAL_NODE$1,
transformFn: undefined,
applyValueToInputSignal(node, value) {
signalSetFn$1(node, value);
},
};
})();
const ɵINPUT_SIGNAL_BRAND_READ_TYPE = /* @__PURE__ */ Symbol();
const ɵINPUT_SIGNAL_BRAND_WRITE_TYPE = /* @__PURE__ */ Symbol();
/**
* Creates an input signal.
*
* @param initialValue The initial value.
* Can be set to {@link REQUIRED_UNSET_VALUE} for required inputs.
* @param options Additional options for the input. e.g. a transform, or an alias.
*/
function createInputSignal(initialValue, options) {
const node = Object.create(INPUT_SIGNAL_NODE);
node.value = initialValue;
// Perf note: Always set `transformFn` here to ensure that `node` always
// has the same v8 class shape, allowing monomorphic reads on input signals.
node.transformFn = options?.transform;
function inputValueFn() {
// Record that someone looked at this signal.
producerAccessed$1(node);
if (node.value === REQUIRED_UNSET_VALUE) {
throw new RuntimeError(-950 /* RuntimeErrorCode.REQUIRED_INPUT_NO_VALUE */, ngDevMode && 'Input is required but no value is available yet.');
}
return node.value;
}
inputValueFn[SIGNAL$1] = node;
if (ngDevMode) {
inputValueFn.toString = () => `[Input Signal: ${inputValueFn()}]`;
}
return inputValueFn;
}
/**
* Convince closure compiler that the wrapped function has no side-effects.
*
* Closure compiler always assumes that `toString` has no side-effects. We use this quirk to
* allow us to execute a function but have closure compiler mark the call as no-side-effects.
* It is important that the return value for the `noSideEffects` function be assigned
* to something which is retained otherwise the call to `noSideEffects` will be removed by closure
* compiler.
*/
function noSideEffects(fn) {
return { toString: fn }.toString();
}
const ANNOTATIONS = '__annotations__';
const PARAMETERS = '__parameters__';
const PROP_METADATA = '__prop__metadata__';
/**
* @suppress {globalThis}
*/
function makeDecorator(name, props, parentClass, additionalProcessing, typeFn) {
return noSideEffects(() => {
const metaCtor = makeMetadataCtor(props);
function DecoratorFactory(...args) {
if (this instanceof DecoratorFactory) {
metaCtor.call(this, ...args);
return this;
}
const annotationInstance = new DecoratorFactory(...args);
return function TypeDecorator(cls) {
if (typeFn)
typeFn(cls, ...args);
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const annotations = cls.hasOwnProperty(ANNOTATIONS)
? cls[ANNOTATIONS]
: Object.defineProperty(cls, ANNOTATIONS, { value: [] })[ANNOTATIONS];
annotations.push(annotationInstance);
if (additionalProcessing)
additionalProcessing(cls);
return cls;
};
}
if (parentClass) {
DecoratorFactory.prototype = Object.create(parentClass.prototype);
}
DecoratorFactory.prototype.ngMetadataName = name;
DecoratorFactory.annotationCls = DecoratorFactory;
return DecoratorFactory;
});
}
function makeMetadataCtor(props) {
return function ctor(...args) {
if (props) {
const values = props(...args);
for (const propName in values) {
this[propName] = values[propName];
}
}
};
}
function makeParamDecorator(name, props, parentClass) {
return noSideEffects(() => {
const metaCtor = makeMetadataCtor(props);
function ParamDecoratorFactory(...args) {
if (this instanceof ParamDecoratorFactory) {
metaCtor.apply(this, args);
return this;
}
const annotationInstance = new ParamDecoratorFactory(...args);
ParamDecorator.annotation = annotationInstance;
return ParamDecorator;
function ParamDecorator(cls, unusedKey, index) {
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const parameters = cls.hasOwnProperty(PARAMETERS)
? cls[PARAMETERS]
: Object.defineProperty(cls, PARAMETERS, { value: [] })[PARAMETERS];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.
while (parameters.length <= index) {
parameters.push(null);
}
(parameters[index] = parameters[index] || []).push(annotationInstance);
return cls;
}
}
if (parentClass) {
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
ParamDecoratorFactory.prototype.ngMetadataName = name;
ParamDecoratorFactory.annotationCls = ParamDecoratorFactory;
return ParamDecoratorFactory;
});
}
function makePropDecorator(name, props, parentClass, additionalProcessing) {
return noSideEffects(() => {
const metaCtor = makeMetadataCtor(props);
function PropDecoratorFactory(...args) {
if (this instanceof PropDecoratorFactory) {
metaCtor.apply(this, args);
return this;
}
const decoratorInstance = new PropDecoratorFactory(...args);
function PropDecorator(target, name) {
// target is undefined with standard decorators. This case is not supported and will throw
// if this decorator is used in JIT mode with standard decorators.
if (target === undefined) {
throw new Error('Standard Angular field decorators are not supported in JIT mode.');
}
const constructor = target.constructor;
// Use of Object.defineProperty is important because it creates a non-enumerable property
// which prevents the property from being copied during subclassing.
const meta = constructor.hasOwnProperty(PROP_METADATA)
? constructor[PROP_METADATA]
: Object.defineProperty(constructor, PROP_METADATA, { value: {} })[PROP_METADATA];
meta[name] = (meta.hasOwnProperty(name) && meta[name]) || [];
meta[name].unshift(decoratorInstance);
if (additionalProcessing)
additionalProcessing(target, name, ...args);
}
return PropDecorator;
}
if (parentClass) {
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
PropDecoratorFactory.prototype.ngMetadataName = name;
PropDecoratorFactory.annotationCls = PropDecoratorFactory;
return PropDecoratorFactory;
});
}
const _global = globalThis;
function ngDevModeResetPerfCounters() {
const locationString = typeof location !== 'undefined' ? location.toString() : '';
const newCounters = {
namedConstructors: locationString.indexOf('ngDevMode=namedConstructors') != -1,
firstCreatePass: 0,
tNode: 0,
tView: 0,
rendererCreateTextNode: 0,
rendererSetText: 0,
rendererCreateElement: 0,
rendererAddEventListener: 0,
rendererSetAttribute: 0,
rendererRemoveAttribute: 0,
rendererSetProperty: 0,
rendererSetClassName: 0,
rendererAddClass: 0,
rendererRemoveClass: 0,
rendererSetStyle: 0,
rendererRemoveStyle: 0,
rendererDestroy: 0,
rendererDestroyNode: 0,
rendererMoveNode: 0,
rendererRemoveNode: 0,
rendererAppendChild: 0,
rendererInsertBefore: 0,
rendererCreateComment: 0,
hydratedNodes: 0,
hydratedComponents: 0,
dehydratedViewsRemoved: 0,
dehydratedViewsCleanupRuns: 0,
componentsSkippedHydration: 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.
*
* NOTE:
* - changes to the `ngDevMode` name must be synced with `compiler-cli/src/tooling.ts`.
*/
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;
}
}
throw Error('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;
}
if (token.overriddenName) {
return `${token.overriddenName}`;
}
if (token.name) {
return `${token.name}`;
}
const res = token.toString();
if (res == null) {
return '' + res;
}
const newLineIndex = res.indexOf('\n');
return newLineIndex === -1 ? res : res.substring(0, newLineIndex);
}
/**
* 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) {
return before == null || before === ''
? after === null
? ''
: after
: after == null || after === ''
? before
: 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
* ```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
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$1() !== 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) || getOwnDefinition(type, NG_INJECTABLE_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) {
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) {
const def = type && (type[NG_PROV_DEF] || type[NG_INJECTABLE_DEF]);
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.hasOwnProperty(NG_INJECTOR_DEF))
? type[NG_INJ_DEF]
: null;
}
const NG_PROV_DEF = getClosureSafeProperty({ ɵprov: getClosureSafeProperty });
const NG_INJ_DEF = getClosureSafeProperty({ ɵinj: getClosureSafeProperty });
// We need to keep these around so we can read off old defs if new defs are unavailable
const NG_INJECTABLE_DEF = getClosureSafeProperty({ ngInjectableDef: getClosureSafeProperty });
const NG_INJECTOR_DEF = getClosureSafeProperty({ ngInjectorDef: 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`.
*
*
*
*
*
* 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 {
/**
* @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;
/** @internal */
this.ngMetadataName = 'InjectionToken';
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;
}
let injectorProfilerCallback = null;
/**
* Sets the callback function which will be invoked during certain DI events within the
* runtime (for example: injecting services, creating injectable instances, configuring providers)
*
* 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.
*/
const setInjectorProfiler = (injectorProfiler) => {
!ngDevMode && throwError('setInjectorProfiler should never be called in production mode');
injectorProfilerCallback = injectorProfiler;
};
/**
* 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');
if (injectorProfilerCallback != null /* both `null` and `undefined` */) {
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 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 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) {
const depPath = path ? `. Dependency path: ${path.join(' > ')} > ${token}` : '';
throw new RuntimeError(-200 /* RuntimeErrorCode.CYCLIC_DI_DEPENDENCY */, ngDevMode ? `Circular dependency in DI detected for ${token}${depPath}` : 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);
}
/**
* Injection flags for DI.
*
* @publicApi
* @deprecated use an options object for [`inject`](api/core/inject) instead.
*/
var InjectFlags;
(function (InjectFlags) {
// TODO(alxhub): make this 'const' (and remove `InternalInjectFlags` enum) when ngc no longer
// writes exports of it into ngfactory files.
/** Check self and check parent injector if needed */
InjectFlags[InjectFlags["Default"] = 0] = "Default";
/**
* Specifies that an injector should retrieve a dependency from any injector until reaching the
* host element of the current component. (Only used with Element Injector)
*/
InjectFlags[InjectFlags["Host"] = 1] = "Host";
/** Don't ascend to ancestors of the node requesting injection. */
InjectFlags[InjectFlags["Self"] = 2] = "Self";
/** Skip the node that is requesting injection. */
InjectFlags[InjectFlags["SkipSelf"] = 4] = "SkipSelf";
/** Inject `defaultValue` instead if token not found. */
InjectFlags[InjectFlags["Optional"] = 8] = "Optional";
})(InjectFlags || (InjectFlags = {}));
/**
* 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 & InjectFlags.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__';
const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
const NG_TOKEN_PATH = 'ngTokenPath';
const NEW_LINE = /\n/gm;
const NO_NEW_LINE = 'ɵ';
const SOURCE = '__source';
/**
* Current injector value used by `inject`.
* - `undefined`: it is an error to call `inject`
* - `null`: `inject` can be called but there is no injector (limp-mode).
* - Injector instance: Use the injector for resolution.
*/
let _currentInjector = undefined;
function getCurrentInjector() {
return _currentInjector;
}
function setCurrentInjector(injector) {
const former = _currentInjector;
_currentInjector = injector;
return former;
}
function injectInjectorOnly(token, flags = InjectFlags.Default) {
if (_currentInjector === undefined) {
throw new RuntimeError(-203 /* RuntimeErrorCode.MISSING_INJECTION_CONTEXT */, ngDevMode &&
`inject() 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 value = _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
ngDevMode && emitInjectEvent(token, value, flags);
return value;
}
}
function ɵɵinject(token, flags = InjectFlags.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:
*
* ```typescript
* @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:
*
* ```typescript
* 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):
*
* ```typescript
* @Component({ ... })
* export class CarComponent {
* ngOnInit() {
* // ERROR: too late, the component instance was already created
* const engine = inject(Engine);
* engine.start();
* }
* }
* ```
*
* @publicApi
*/
function inject(token, flags = InjectFlags.Default) {
// 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(flags));
}
// 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 */));
}
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 = InjectFlags.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 ')}`;
}
/**
* Inject decorator and metadata.
*
* @Annotation
* @publicApi
*/
const Inject = attachInjectFlag(
// Disable tslint because `DecoratorFlags` is a const enum which gets inlined.
makeParamDecorator('Inject', (token) => ({ token })), -1 /* DecoratorFlags.Inject */);
/**
* Optional decorator and metadata.
*
* @Annotation
* @publicApi
*/
const Optional =
// Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
// tslint:disable-next-line: no-toplevel-property-access
attachInjectFlag(makeParamDecorator('Optional'), 8 /* InternalInjectFlags.Optional */);
/**
* Self decorator and metadata.
*
* @Annotation
* @publicApi
*/
const Self =
// Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
// tslint:disable-next-line: no-toplevel-property-access
attachInjectFlag(makeParamDecorator('Self'), 2 /* InternalInjectFlags.Self */);
/**
* `SkipSelf` decorator and metadata.
*
* @Annotation
* @publicApi
*/
const SkipSelf =
// Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
// tslint:disable-next-line: no-toplevel-property-access
attachInjectFlag(makeParamDecorator('SkipSelf'), 4 /* InternalInjectFlags.SkipSelf */);
/**
* Host decorator and metadata.
*
* @Annotation
* @publicApi
*/
const Host =
// Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
// tslint:disable-next-line: no-toplevel-property-access
attachInjectFlag(makeParamDecorator('Host'), 1 /* InternalInjectFlags.Host */);
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.splice(index, 0, value)` 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 value Value to add to array.
*/
function arrayInsert(array, index, value) {
ngDevMode && assertLessThanOrEqual(index, array.length, "Can't insert past array end.");
let end = array.length;
while (end > index) {
const previousEnd = end - 1;
array[end] = array[previousEnd];
end = previousEnd;
}
array[index] = value;
}
/**
* 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;
}
}
/**
* Get an index of an `value` in a sorted `array`.
*
* NOTE:
* - This uses binary search algorithm for fast removals.
*
* @param array A sorted array to binary search.
* @param value The value to look for.
* @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
* located)
*/
function arrayIndexOfSorted(array, value) {
return _arrayIndexOfSorted(array, value, 0);
}
/**
* 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);
}
/**
* Delete a `key` (and `value`) from the `KeyValueArray`.
*
* @param keyValueArray to modify.
* @param key The key to locate or delete (if exist).
* @returns index of where the key was (or should have been.)
* - positive (even) index if key found and deleted.
* - negative index if key not found. (`~index` (even) to get the index where it should have
* been.)
*/
function keyValueArrayDelete(keyValueArray, key) {
const index = keyValueArrayIndexOf(keyValueArray, key);
if (index >= 0) {
// if we found it remove it.
arraySplice(keyValueArray, index, 2);
}
return index;
}
/**
* 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.
*
* 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 Error(`NullInjectorError: No provider for ${stringify(token)}!`);
error.name = 'NullInjectorError';
throw error;
}
return notFoundValue;
}
}
/**
* The strategy that the default change detector uses to detect changes.
* When set, takes effect the next time change detection is triggered.
*
* @see [Change detection usage](/api/core/ChangeDetectorRef?tab=usage-notes)
* @see [Skipping component subtrees](/best-practices/skipping-subtrees)
*
* @publicApi
*/
var ChangeDetectionStrategy;
(function (ChangeDetectionStrategy) {
/**
* Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated
* until reactivated by setting the strategy to `Default` (`CheckAlways`).
* Change detection can still be explicitly invoked.
* This strategy applies to all child directives and cannot be overridden.
*/
ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush";
/**
* Use the default `CheckAlways` strategy, in which change detection is automatic until
* explicitly deactivated.
*/
ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default";
})(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}));
/**
* Defines the CSS styles encapsulation policies for the {@link Component} decorator's
* `encapsulation` option.
*
* See {@link Component#encapsulation encapsulation}.
*
* @usageNotes
* ### Example
*
* {@example core/ts/metadata/encapsulation.ts region='longform'}
*
* @publicApi
*/
var ViewEncapsulation$1;
(function (ViewEncapsulation) {
// TODO: consider making `ViewEncapsulation` a `const enum` instead. See
// https://github.com/angular/angular/issues/44119 for additional information.
/**
* Emulates a native Shadow DOM encapsulation behavior by adding a specific attribute to the
* component's host element and applying the same attribute to all the CSS selectors provided
* via {@link Component#styles styles} or {@link Component#styleUrls styleUrls}.
*
* This is the default option.
*/
ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
/**
* Doesn't provide any sort of CSS style encapsulation, meaning that all the styles provided
* via {@link Component#styles styles} or {@link Component#styleUrls styleUrls} are applicable
* to any HTML element of the application regardless of their host Component.
*/
ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None";
/**
* Uses the browser's native Shadow DOM API to encapsulate CSS styles, meaning that it creates
* a ShadowRoot for the component's host element which is then used to encapsulate
* all the Component's styling.
*/
ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom";
})(ViewEncapsulation$1 || (ViewEncapsulation$1 = {}));
/** Flags describing an input for a directive. */
var InputFlags;
(function (InputFlags) {
InputFlags[InputFlags["None"] = 0] = "None";
InputFlags[InputFlags["SignalBased"] = 1] = "SignalBased";
InputFlags[InputFlags["HasDecoratorInputTransform"] = 2] = "HasDecoratorInputTransform";
})(InputFlags || (InputFlags = {}));
/**
* Returns an index of `classToSearch` in `className` taking token boundaries into account.
*
* `classIndexOf('AB A', 'A', 0)` will be 3 (not 0 since `AB!==A`)
*
* @param className A string containing classes (whitespace separated)
* @param classToSearch A class name to locate
* @param startingIndex Starting location of search
* @returns an index of the located class (or -1 if not found)
*/
function classIndexOf(className, classToSearch, startingIndex) {
ngDevMode && assertNotEqual(classToSearch, '', 'can not look for "" string.');
let end = className.length;
while (true) {
const foundIndex = className.indexOf(classToSearch, startingIndex);
if (foundIndex === -1)
return foundIndex;
if (foundIndex === 0 || className.charCodeAt(foundIndex - 1) <= 32 /* CharCode.SPACE */) {
// Ensure that it has leading whitespace
const length = classToSearch.length;
if (foundIndex + length === end ||
className.charCodeAt(foundIndex + length) <= 32 /* CharCode.SPACE */) {
// Ensure that it has trailing whitespace
return foundIndex;
}
}
// False positive, keep searching from where we left off.
startingIndex = foundIndex + 1;
}
}
/**
* Assigns all attribute values to the provided element via the inferred renderer.
*
* This function accepts two forms of attribute entries:
*
* default: (key, value):
* attrs = [key1, value1, key2, value2]
*
* namespaced: (NAMESPACE_MARKER, uri, name, value)
* attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value]
*
* The `attrs` array can contain a mix of both the default and namespaced entries.
* The "default" values are set without a marker, but if the function comes across
* a marker value then it will attempt to set a namespaced value. If the marker is
* not of a namespaced value then the function will quit and return the index value
* where it stopped during the iteration of the attrs array.
*
* See [AttributeMarker] to understand what the namespace marker value is.
*
* Note that this instruction does not support assigning style and class values to
* an element. See `elementStart` and `elementHostAttrs` to learn how styling values
* are applied to an element.
* @param renderer The renderer to be used
* @param native The element that the attributes will be assigned to
* @param attrs The attribute array of values that will be assigned to the element
* @returns the index value that was last accessed in the attributes array
*/
function setUpAttributes(renderer, native, attrs) {
let i = 0;
while (i < attrs.length) {
const value = attrs[i];
if (typeof value === 'number') {
// only namespaces are supported. Other value types (such as style/class
// entries) are not supported in this function.
if (value !== 0 /* AttributeMarker.NamespaceURI */) {
break;
}
// we just landed on the marker value ... therefore
// we should skip to the next entry
i++;
const namespaceURI = attrs[i++];
const attrName = attrs[i++];
const attrVal = attrs[i++];
ngDevMode && ngDevMode.rendererSetAttribute++;
renderer.setAttribute(native, attrName, attrVal, namespaceURI);
}
else {
// attrName is string;
const attrName = value;
const attrVal = attrs[++i];
// Standard attributes
ngDevMode && ngDevMode.rendererSetAttribute++;
if (isAnimationProp(attrName)) {
renderer.setProperty(native, attrName, attrVal);
}
else {
renderer.setAttribute(native, attrName, attrVal);
}
i++;
}
}
// another piece of code may iterate over the same attributes array. Therefore
// it may be helpful to return the exact spot where the attributes array exited
// whether by running into an unsupported marker or if all the static values were
// iterated over.
return i;
}
/**
* Test whether the given value is a marker that indicates that the following
* attribute values in a `TAttributes` array are only the names of attributes,
* and not name-value pairs.
* @param marker The attribute marker to test.
* @returns true if the marker is a "name-only" marker (e.g. `Bindings`, `Template` or `I18n`).
*/
function isNameOnlyAttributeMarker(marker) {
return (marker === 3 /* AttributeMarker.Bindings */ ||
marker === 4 /* AttributeMarker.Template */ ||
marker === 6 /* AttributeMarker.I18n */);
}
function isAnimationProp(name) {
// Perf note: accessing charCodeAt to check for the first character of a string is faster as
// compared to accessing a character at index 0 (ex. name[0]). The main reason for this is that
// charCodeAt doesn't allocate memory to return a substring.
return name.charCodeAt(0) === 64 /* CharCode.AT_SIGN */;
}
/**
* Merges `src` `TAttributes` into `dst` `TAttributes` removing any duplicates in the process.
*
* This merge function keeps the order of attrs same.
*
* @param dst Location of where the merged `TAttributes` should end up.
* @param src `TAttributes` which should be appended to `dst`
*/
function mergeHostAttrs(dst, src) {
if (src === null || src.length === 0) {
// do nothing
}
else if (dst === null || dst.length === 0) {
// We have source, but dst is empty, just make a copy.
dst = src.slice();
}
else {
let srcMarker = -1 /* AttributeMarker.ImplicitAttributes */;
for (let i = 0; i < src.length; i++) {
const item = src[i];
if (typeof item === 'number') {
srcMarker = item;
}
else {
if (srcMarker === 0 /* AttributeMarker.NamespaceURI */) {
// Case where we need to consume `key1`, `key2`, `value` items.
}
else if (srcMarker === -1 /* AttributeMarker.ImplicitAttributes */ ||
srcMarker === 2 /* AttributeMarker.Styles */) {
// Case where we have to consume `key1` and `value` only.
mergeHostAttribute(dst, srcMarker, item, null, src[++i]);
}
else {
// Case where we have to consume `key1` only.
mergeHostAttribute(dst, srcMarker, item, null, null);
}
}
}
}
return dst;
}
/**
* Append `key`/`value` to existing `TAttributes` taking region marker and duplicates into account.
*
* @param dst `TAttributes` to append to.
* @param marker Region where the `key`/`value` should be added.
* @param key1 Key to add to `TAttributes`
* @param key2 Key to add to `TAttributes` (in case of `AttributeMarker.NamespaceURI`)
* @param value Value to add or to overwrite to `TAttributes` Only used if `marker` is not Class.
*/
function mergeHostAttribute(dst, marker, key1, key2, value) {
let i = 0;
// Assume that new markers will be inserted at the end.
let markerInsertPosition = dst.length;
// scan until correct type.
if (marker === -1 /* AttributeMarker.ImplicitAttributes */) {
markerInsertPosition = -1;
}
else {
while (i < dst.length) {
const dstValue = dst[i++];
if (typeof dstValue === 'number') {
if (dstValue === marker) {
markerInsertPosition = -1;
break;
}
else if (dstValue > marker) {
// We need to save this as we want the markers to be inserted in specific order.
markerInsertPosition = i - 1;
break;
}
}
}
}
// search until you find place of insertion
while (i < dst.length) {
const item = dst[i];
if (typeof item === 'number') {
// since `i` started as the index after the marker, we did not find it if we are at the next
// marker
break;
}
else if (item === key1) {
// We already have same token
if (key2 === null) {
if (value !== null) {
dst[i + 1] = value;
}
return;
}
else if (key2 === dst[i + 1]) {
dst[i + 2] = value;
return;
}
}
// Increment counter.
i++;
if (key2 !== null)
i++;
if (value !== null)
i++;
}
// insert at location.
if (markerInsertPosition !== -1) {
dst.splice(markerInsertPosition, 0, marker);
i = markerInsertPosition + 1;
}
dst.splice(i++, 0, key1);
if (key2 !== null) {
dst.splice(i++, 0, key2);
}
if (value !== null) {
dst.splice(i++, 0, value);
}
}
const NG_TEMPLATE_SELECTOR = 'ng-template';
/**
* Search the `TAttributes` to see if it contains `cssClassToMatch` (case insensitive)
*
* @param tNode static data of the node to match
* @param attrs `TAttributes` to search through.
* @param cssClassToMatch class to match (lowercase)
* @param isProjectionMode Whether or not class matching should look into the attribute `class` in
* addition to the `AttributeMarker.Classes`.
*/
function isCssClassMatching(tNode, attrs, cssClassToMatch, isProjectionMode) {
ngDevMode &&
assertEqual(cssClassToMatch, cssClassToMatch.toLowerCase(), 'Class name expected to be lowercase.');
let i = 0;
if (isProjectionMode) {
for (; i < attrs.length && typeof attrs[i] === 'string'; i += 2) {
// Search for an implicit `class` attribute and check if its value matches `cssClassToMatch`.
if (attrs[i] === 'class' &&
classIndexOf(attrs[i + 1].toLowerCase(), cssClassToMatch, 0) !== -1) {
return true;
}
}
}
else if (isInlineTemplate(tNode)) {
// Matching directives (i.e. when not matching for projection mode) should not consider the
// class bindings that are present on inline templates, as those class bindings only target
// the root node of the template, not the template itself.
return false;
}
// Resume the search for classes after the `Classes` marker.
i = attrs.indexOf(1 /* AttributeMarker.Classes */, i);
if (i > -1) {
// We found the classes section. Start searching for the class.
let item;
while (++i < attrs.length && typeof (item = attrs[i]) === 'string') {
if (item.toLowerCase() === cssClassToMatch) {
return true;
}
}
}
return false;
}
/**
* Checks whether the `tNode` represents an inline template (e.g. `*ngFor`).
*
* @param tNode current TNode
*/
function isInlineTemplate(tNode) {
return tNode.type === 4 /* TNodeType.Container */ && tNode.value !== NG_TEMPLATE_SELECTOR;
}
/**
* Function that checks whether a given tNode matches tag-based selector and has a valid type.
*
* Matching can be performed in 2 modes: projection mode (when we project nodes) and regular
* directive matching mode:
* - in the "directive matching" mode we do _not_ take TContainer's tagName into account if it is
* different from NG_TEMPLATE_SELECTOR (value different from NG_TEMPLATE_SELECTOR indicates that a
* tag name was extracted from * syntax so we would match the same directive twice);
* - in the "projection" mode, we use a tag name potentially extracted from the * syntax processing
* (applicable to TNodeType.Container only).
*/
function hasTagAndTypeMatch(tNode, currentSelector, isProjectionMode) {
const tagNameToCompare = tNode.type === 4 /* TNodeType.Container */ && !isProjectionMode ? NG_TEMPLATE_SELECTOR : tNode.value;
return currentSelector === tagNameToCompare;
}
/**
* A utility function to match an Ivy node static data against a simple CSS selector
*
* @param tNode static data of the node to match
* @param selector The selector to try matching against the node.
* @param isProjectionMode if `true` we are matching for content projection, otherwise we are doing
* directive matching.
* @returns true if node matches the selector.
*/
function isNodeMatchingSelector(tNode, selector, isProjectionMode) {
ngDevMode && assertDefined(selector[0], 'Selector should have a tag name');
let mode = 4 /* SelectorFlags.ELEMENT */;
const nodeAttrs = tNode.attrs;
// Find the index of first attribute that has no value, only a name.
const nameOnlyMarkerIdx = nodeAttrs !== null ? getNameOnlyMarkerIndex(nodeAttrs) : 0;
// When processing ":not" selectors, we skip to the next ":not" if the
// current one doesn't match
let skipToNextSelector = false;
for (let i = 0; i < selector.length; i++) {
const current = selector[i];
if (typeof current === 'number') {
// If we finish processing a :not selector and it hasn't failed, return false
if (!skipToNextSelector && !isPositive(mode) && !isPositive(current)) {
return false;
}
// If we are skipping to the next :not() and this mode flag is positive,
// it's a part of the current :not() selector, and we should keep skipping
if (skipToNextSelector && isPositive(current))
continue;
skipToNextSelector = false;
mode = current | (mode & 1 /* SelectorFlags.NOT */);
continue;
}
if (skipToNextSelector)
continue;
if (mode & 4 /* SelectorFlags.ELEMENT */) {
mode = 2 /* SelectorFlags.ATTRIBUTE */ | (mode & 1 /* SelectorFlags.NOT */);
if ((current !== '' && !hasTagAndTypeMatch(tNode, current, isProjectionMode)) ||
(current === '' && selector.length === 1)) {
if (isPositive(mode))
return false;
skipToNextSelector = true;
}
}
else if (mode & 8 /* SelectorFlags.CLASS */) {
if (nodeAttrs === null || !isCssClassMatching(tNode, nodeAttrs, current, isProjectionMode)) {
if (isPositive(mode))
return false;
skipToNextSelector = true;
}
}
else {
const selectorAttrValue = selector[++i];
const attrIndexInNode = findAttrIndexInNode(current, nodeAttrs, isInlineTemplate(tNode), isProjectionMode);
if (attrIndexInNode === -1) {
if (isPositive(mode))
return false;
skipToNextSelector = true;
continue;
}
if (selectorAttrValue !== '') {
let nodeAttrValue;
if (attrIndexInNode > nameOnlyMarkerIdx) {
nodeAttrValue = '';
}
else {
ngDevMode &&
assertNotEqual(nodeAttrs[attrIndexInNode], 0 /* AttributeMarker.NamespaceURI */, 'We do not match directives on namespaced attributes');
// we lowercase the attribute value to be able to match
// selectors without case-sensitivity
// (selectors are already in lowercase when generated)
nodeAttrValue = nodeAttrs[attrIndexInNode + 1].toLowerCase();
}
if (mode & 2 /* SelectorFlags.ATTRIBUTE */ && selectorAttrValue !== nodeAttrValue) {
if (isPositive(mode))
return false;
skipToNextSelector = true;
}
}
}
}
return isPositive(mode) || skipToNextSelector;
}
function isPositive(mode) {
return (mode & 1 /* SelectorFlags.NOT */) === 0;
}
/**
* Examines the attribute's definition array for a node to find the index of the
* attribute that matches the given `name`.
*
* NOTE: This will not match namespaced attributes.
*
* Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`.
* The following table summarizes which types of attributes we attempt to match:
*
* ===========================================================================================================
* Modes | Normal Attributes | Bindings Attributes | Template Attributes | I18n
* Attributes
* ===========================================================================================================
* Inline + Projection | YES | YES | NO | YES
* -----------------------------------------------------------------------------------------------------------
* Inline + Directive | NO | NO | YES | NO
* -----------------------------------------------------------------------------------------------------------
* Non-inline + Projection | YES | YES | NO | YES
* -----------------------------------------------------------------------------------------------------------
* Non-inline + Directive | YES | YES | NO | YES
* ===========================================================================================================
*
* @param name the name of the attribute to find
* @param attrs the attribute array to examine
* @param isInlineTemplate true if the node being matched is an inline template (e.g. `*ngFor`)
* rather than a manually expanded template node (e.g ``).
* @param isProjectionMode true if we are matching against content projection otherwise we are
* matching against directives.
*/
function findAttrIndexInNode(name, attrs, isInlineTemplate, isProjectionMode) {
if (attrs === null)
return -1;
let i = 0;
if (isProjectionMode || !isInlineTemplate) {
let bindingsMode = false;
while (i < attrs.length) {
const maybeAttrName = attrs[i];
if (maybeAttrName === name) {
return i;
}
else if (maybeAttrName === 3 /* AttributeMarker.Bindings */ ||
maybeAttrName === 6 /* AttributeMarker.I18n */) {
bindingsMode = true;
}
else if (maybeAttrName === 1 /* AttributeMarker.Classes */ ||
maybeAttrName === 2 /* AttributeMarker.Styles */) {
let value = attrs[++i];
// We should skip classes here because we have a separate mechanism for
// matching classes in projection mode.
while (typeof value === 'string') {
value = attrs[++i];
}
continue;
}
else if (maybeAttrName === 4 /* AttributeMarker.Template */) {
// We do not care about Template attributes in this scenario.
break;
}
else if (maybeAttrName === 0 /* AttributeMarker.NamespaceURI */) {
// Skip the whole namespaced attribute and value. This is by design.
i += 4;
continue;
}
// In binding mode there are only names, rather than name-value pairs.
i += bindingsMode ? 1 : 2;
}
// We did not match the attribute
return -1;
}
else {
return matchTemplateAttribute(attrs, name);
}
}
function isNodeMatchingSelectorList(tNode, selector, isProjectionMode = false) {
for (let i = 0; i < selector.length; i++) {
if (isNodeMatchingSelector(tNode, selector[i], isProjectionMode)) {
return true;
}
}
return false;
}
function getProjectAsAttrValue(tNode) {
const nodeAttrs = tNode.attrs;
if (nodeAttrs != null) {
const ngProjectAsAttrIdx = nodeAttrs.indexOf(5 /* AttributeMarker.ProjectAs */);
// only check for ngProjectAs in attribute names, don't accidentally match attribute's value
// (attribute names are stored at even indexes)
if ((ngProjectAsAttrIdx & 1) === 0) {
return nodeAttrs[ngProjectAsAttrIdx + 1];
}
}
return null;
}
function getNameOnlyMarkerIndex(nodeAttrs) {
for (let i = 0; i < nodeAttrs.length; i++) {
const nodeAttr = nodeAttrs[i];
if (isNameOnlyAttributeMarker(nodeAttr)) {
return i;
}
}
return nodeAttrs.length;
}
function matchTemplateAttribute(attrs, name) {
let i = attrs.indexOf(4 /* AttributeMarker.Template */);
if (i > -1) {
i++;
while (i < attrs.length) {
const attr = attrs[i];
// Return in case we checked all template attrs and are switching to the next section in the
// attrs array (that starts with a number that represents an attribute marker).
if (typeof attr === 'number')
return -1;
if (attr === name)
return i;
i++;
}
}
return -1;
}
/**
* Checks whether a selector is inside a CssSelectorList
* @param selector Selector to be checked.
* @param list List in which to look for the selector.
*/
function isSelectorInSelectorList(selector, list) {
selectorListLoop: for (let i = 0; i < list.length; i++) {
const currentSelectorInList = list[i];
if (selector.length !== currentSelectorInList.length) {
continue;
}
for (let j = 0; j < selector.length; j++) {
if (selector[j] !== currentSelectorInList[j]) {
continue selectorListLoop;
}
}
return true;
}
return false;
}
function maybeWrapInNotSelector(isNegativeMode, chunk) {
return isNegativeMode ? ':not(' + chunk.trim() + ')' : chunk;
}
function stringifyCSSSelector(selector) {
let result = selector[0];
let i = 1;
let mode = 2 /* SelectorFlags.ATTRIBUTE */;
let currentChunk = '';
let isNegativeMode = false;
while (i < selector.length) {
let valueOrMarker = selector[i];
if (typeof valueOrMarker === 'string') {
if (mode & 2 /* SelectorFlags.ATTRIBUTE */) {
const attrValue = selector[++i];
currentChunk +=
'[' + valueOrMarker + (attrValue.length > 0 ? '="' + attrValue + '"' : '') + ']';
}
else if (mode & 8 /* SelectorFlags.CLASS */) {
currentChunk += '.' + valueOrMarker;
}
else if (mode & 4 /* SelectorFlags.ELEMENT */) {
currentChunk += ' ' + valueOrMarker;
}
}
else {
//
// Append current chunk to the final result in case we come across SelectorFlag, which
// indicates that the previous section of a selector is over. We need to accumulate content
// between flags to make sure we wrap the chunk later in :not() selector if needed, e.g.
// ```
// ['', Flags.CLASS, '.classA', Flags.CLASS | Flags.NOT, '.classB', '.classC']
// ```
// should be transformed to `.classA :not(.classB .classC)`.
//
// Note: for negative selector part, we accumulate content between flags until we find the
// next negative flag. This is needed to support a case where `:not()` rule contains more than
// one chunk, e.g. the following selector:
// ```
// ['', Flags.ELEMENT | Flags.NOT, 'p', Flags.CLASS, 'foo', Flags.CLASS | Flags.NOT, 'bar']
// ```
// should be stringified to `:not(p.foo) :not(.bar)`
//
if (currentChunk !== '' && !isPositive(valueOrMarker)) {
result += maybeWrapInNotSelector(isNegativeMode, currentChunk);
currentChunk = '';
}
mode = valueOrMarker;
// According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative
// mode is maintained for remaining chunks of a selector.
isNegativeMode = isNegativeMode || !isPositive(mode);
}
i++;
}
if (currentChunk !== '') {
result += maybeWrapInNotSelector(isNegativeMode, currentChunk);
}
return result;
}
/**
* Generates string representation of CSS selector in parsed form.
*
* ComponentDef and DirectiveDef are generated with the selector in parsed form to avoid doing
* additional parsing at runtime (for example, for directive matching). However in some cases (for
* example, while bootstrapping a component), a string version of the selector is required to query
* for the host element on the page. This function takes the parsed form of a selector and returns
* its string representation.
*
* @param selectorList selector in parsed form
* @returns string representation of a given selector
*/
function stringifyCSSSelectorList(selectorList) {
return selectorList.map(stringifyCSSSelector).join(',');
}
/**
* Extracts attributes and classes information from a given CSS selector.
*
* This function is used while creating a component dynamically. In this case, the host element
* (that is created dynamically) should contain attributes and classes specified in component's CSS
* selector.
*
* @param selector CSS selector in parsed form (in a form of array)
* @returns object with `attrs` and `classes` fields that contain extracted information
*/
function extractAttrsAndClassesFromSelector(selector) {
const attrs = [];
const classes = [];
let i = 1;
let mode = 2 /* SelectorFlags.ATTRIBUTE */;
while (i < selector.length) {
let valueOrMarker = selector[i];
if (typeof valueOrMarker === 'string') {
if (mode === 2 /* SelectorFlags.ATTRIBUTE */) {
if (valueOrMarker !== '') {
attrs.push(valueOrMarker, selector[++i]);
}
}
else if (mode === 8 /* SelectorFlags.CLASS */) {
classes.push(valueOrMarker);
}
}
else {
// According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative
// mode is maintained for remaining chunks of a selector. Since attributes and classes are
// extracted only for "positive" part of the selector, we can stop here.
if (!isPositive(mode))
break;
mode = valueOrMarker;
}
i++;
}
return { attrs, classes };
}
/**
* Create a component definition object.
*
*
* # Example
* ```
* class MyComponent {
* // Generated by Angular Template Compiler
* // [Symbol] syntax will not be supported by TypeScript until v2.7
* static ɵcmp = defineComponent({
* ...
* });
* }
* ```
* @codeGenApi
*/
function ɵɵdefineComponent(componentDefinition) {
return noSideEffects(() => {
// Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
// See the `initNgDevMode` docstring for more information.
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
const baseDef = getNgDirectiveDef(componentDefinition);
const def = {
...baseDef,
decls: componentDefinition.decls,
vars: componentDefinition.vars,
template: componentDefinition.template,
consts: componentDefinition.consts || null,
ngContentSelectors: componentDefinition.ngContentSelectors,
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
directiveDefs: null, // assigned in noSideEffects
pipeDefs: null, // assigned in noSideEffects
dependencies: (baseDef.standalone && componentDefinition.dependencies) || null,
getStandaloneInjector: null,
signals: componentDefinition.signals ?? false,
data: componentDefinition.data || {},
encapsulation: componentDefinition.encapsulation || ViewEncapsulation$1.Emulated,
styles: componentDefinition.styles || EMPTY_ARRAY,
_: null,
schemas: componentDefinition.schemas || null,
tView: null,
id: '',
};
initFeatures(def);
const dependencies = componentDefinition.dependencies;
def.directiveDefs = extractDefListOrFactory(dependencies, /* pipeDef */ false);
def.pipeDefs = extractDefListOrFactory(dependencies, /* pipeDef */ true);
def.id = getComponentId(def);
return def;
});
}
function extractDirectiveDef(type) {
return getComponentDef(type) || getDirectiveDef(type);
}
function nonNull(value) {
return value !== null;
}
/**
* @codeGenApi
*/
function ɵɵdefineNgModule(def) {
return noSideEffects(() => {
const res = {
type: def.type,
bootstrap: def.bootstrap || EMPTY_ARRAY,
declarations: def.declarations || EMPTY_ARRAY,
imports: def.imports || EMPTY_ARRAY,
exports: def.exports || EMPTY_ARRAY,
transitiveCompileScopes: null,
schemas: def.schemas || null,
id: def.id || null,
};
return res;
});
}
function parseAndConvertBindingsForDefinition(obj, declaredInputs) {
if (obj == null)
return EMPTY_OBJ;
const newLookup = {};
for (const minifiedKey in obj) {
if (obj.hasOwnProperty(minifiedKey)) {
const value = obj[minifiedKey];
let publicName;
let declaredName;
let inputFlags = InputFlags.None;
if (Array.isArray(value)) {
inputFlags = value[0];
publicName = value[1];
declaredName = value[2] ?? publicName; // declared name might not be set to save bytes.
}
else {
publicName = value;
declaredName = value;
}
// For inputs, capture the declared name, or if some flags are set.
if (declaredInputs) {
// Perf note: An array is only allocated for the input if there are flags.
newLookup[publicName] =
inputFlags !== InputFlags.None ? [minifiedKey, inputFlags] : minifiedKey;
declaredInputs[publicName] = declaredName;
}
else {
newLookup[publicName] = minifiedKey;
}
}
}
return newLookup;
}
/**
* Create a directive definition object.
*
* # Example
* ```ts
* class MyDirective {
* // Generated by Angular Template Compiler
* // [Symbol] syntax will not be supported by TypeScript until v2.7
* static ɵdir = ɵɵdefineDirective({
* ...
* });
* }
* ```
*
* @codeGenApi
*/
function ɵɵdefineDirective(directiveDefinition) {
return noSideEffects(() => {
const def = getNgDirectiveDef(directiveDefinition);
initFeatures(def);
return def;
});
}
/**
* Create a pipe definition object.
*
* # Example
* ```
* class MyPipe implements PipeTransform {
* // Generated by Angular Template Compiler
* static ɵpipe = definePipe({
* ...
* });
* }
* ```
* @param pipeDef Pipe definition generated by the compiler
*
* @codeGenApi
*/
function ɵɵdefinePipe(pipeDef) {
return {
type: pipeDef.type,
name: pipeDef.name,
factory: null,
pure: pipeDef.pure !== false,
standalone: pipeDef.standalone === true,
onDestroy: pipeDef.type.prototype.ngOnDestroy || null,
};
}
/**
* 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 getDirectiveDef(type) {
return type[NG_DIR_DEF] || null;
}
function getPipeDef$1(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$1(type);
return def !== null ? def.standalone : false;
}
function getNgModuleDef(type, throwNotFound) {
const ngModuleDef = type[NG_MOD_DEF] || null;
if (!ngModuleDef && throwNotFound === true) {
throw new Error(`Type ${stringify(type)} does not have 'ɵmod' property.`);
}
return ngModuleDef;
}
function getNgDirectiveDef(directiveDefinition) {
const declaredInputs = {};
return {
type: directiveDefinition.type,
providersResolver: null,
factory: null,
hostBindings: directiveDefinition.hostBindings || null,
hostVars: directiveDefinition.hostVars || 0,
hostAttrs: directiveDefinition.hostAttrs || null,
contentQueries: directiveDefinition.contentQueries || null,
declaredInputs: declaredInputs,
inputTransforms: null,
inputConfig: directiveDefinition.inputs || EMPTY_OBJ,
exportAs: directiveDefinition.exportAs || null,
standalone: directiveDefinition.standalone === true,
signals: directiveDefinition.signals === true,
selectors: directiveDefinition.selectors || EMPTY_ARRAY,
viewQuery: directiveDefinition.viewQuery || null,
features: directiveDefinition.features || null,
setInput: null,
findHostDirectiveDefs: null,
hostDirectives: null,
inputs: parseAndConvertBindingsForDefinition(directiveDefinition.inputs, declaredInputs),
outputs: parseAndConvertBindingsForDefinition(directiveDefinition.outputs),
debugInfo: null,
};
}
function initFeatures(definition) {
definition.features?.forEach((fn) => fn(definition));
}
function extractDefListOrFactory(dependencies, pipeDef) {
if (!dependencies) {
return null;
}
const defExtractor = pipeDef ? getPipeDef$1 : extractDirectiveDef;
return () => (typeof dependencies === 'function' ? dependencies() : dependencies)
.map((dep) => defExtractor(dep))
.filter(nonNull);
}
/**
* A map that contains the generated component IDs and type.
*/
const GENERATED_COMP_IDS = new Map();
/**
* A method can returns a component ID from the component definition using a variant of DJB2 hash
* algorithm.
*/
function getComponentId(componentDef) {
let hash = 0;
// We cannot rely solely on the component selector as the same selector can be used in different
// modules.
//
// `componentDef.style` is not used, due to it causing inconsistencies. Ex: when server
// component styles has no sourcemaps and browsers do.
//
// Example:
// https://github.com/angular/components/blob/d9f82c8f95309e77a6d82fd574c65871e91354c2/src/material/core/option/option.ts#L248
// https://github.com/angular/components/blob/285f46dc2b4c5b127d356cb7c4714b221f03ce50/src/material/legacy-core/option/option.ts#L32
const hashSelectors = [
componentDef.selectors,
componentDef.ngContentSelectors,
componentDef.hostVars,
componentDef.hostAttrs,
componentDef.consts,
componentDef.vars,
componentDef.decls,
componentDef.encapsulation,
componentDef.standalone,
componentDef.signals,
componentDef.exportAs,
JSON.stringify(componentDef.inputs),
JSON.stringify(componentDef.outputs),
// We cannot use 'componentDef.type.name' as the name of the symbol will change and will not
// match in the server and browser bundles.
Object.getOwnPropertyNames(componentDef.type.prototype),
!!componentDef.contentQueries,
!!componentDef.viewQuery,
].join('|');
for (const char of hashSelectors) {
hash = (Math.imul(31, hash) + char.charCodeAt(0)) << 0;
}
// Force positive number hash.
// 2147483647 = equivalent of Integer.MAX_VALUE.
hash += 2147483647 + 1;
const compId = 'c' + hash;
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (GENERATED_COMP_IDS.has(compId)) {
const previousCompDefType = GENERATED_COMP_IDS.get(compId);
if (previousCompDefType !== componentDef.type) {
console.warn(formatRuntimeError(-912 /* RuntimeErrorCode.COMPONENT_ID_COLLISION */, `Component ID generation collision detected. Components '${previousCompDefType.name}' and '${componentDef.type.name}' with selector '${stringifyCSSSelectorList(componentDef.selectors)}' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID.`));
}
}
else {
GENERATED_COMP_IDS.set(compId, componentDef.type);
}
}
return compId;
}
/**
* Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally
* referenced in `@Component` in a component injector.
*/
function makeEnvironmentProviders(providers) {
return {
ɵproviders: providers,
};
}
/**
* 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:
*
* ```typescript
* 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:
*
* ```typescript
* 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$1 = getClosureSafeProperty({
provide: String,
useValue: getClosureSafeProperty,
});
function isValueProvider(value) {
return value !== null && typeof value == 'object' && USE_VALUE$1 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.
*/
class EnvironmentInjector {
}
class R3Injector extends EnvironmentInjector {
/**
* Flag indicating that this injector was previously destroyed.
*/
get destroyed() {
return this._destroyed;
}
constructor(providers, parent, source, scopes) {
super();
this.parent = parent;
this.source = source;
this.scopes = 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.
*/
this.records = new Map();
/**
* Set of values instantiated by this injector which contain `ngOnDestroy` lifecycle hooks.
*/
this._ngOnDestroyHooks = new Set();
this._onDestroyHooks = [];
this._destroyed = false;
// 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, InjectFlags.Self));
}
/**
* 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() {
this.assertNotDestroyed();
// Set destroyed = true first, in case lifecycle hooks re-enter destroy().
this._destroyed = true;
const prevConsumer = setActiveConsumer$1(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$1(prevConsumer);
}
}
onDestroy(callback) {
this.assertNotDestroyed();
this._onDestroyHooks.push(callback);
return () => this.removeOnDestroy(callback);
}
runInContext(fn) {
this.assertNotDestroyed();
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, flags = InjectFlags.Default) {
this.assertNotDestroyed();
if (token.hasOwnProperty(NG_ENV_ID)) {
return token[NG_ENV_ID](this);
}
flags = convertToBitFlags(flags);
// 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 & InjectFlags.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 & InjectFlags.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 & InjectFlags.Optional && notFoundValue === THROW_IF_NOT_FOUND ? null : notFoundValue;
return nextInjector.get(token, notFoundValue);
}
catch (e) {
if (e.name === 'NullInjectorError') {
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$1(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, InjectFlags.Self);
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$1(prevConsumer);
}
}
toString() {
const tokens = [];
const records = this.records;
for (const token of records.keys()) {
tokens.push(stringify(token));
}
return `R3Injector[${tokens.join(', ')}]`;
}
assertNotDestroyed() {
if (this._destroyed) {
throw new RuntimeError(205 /* RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED */, ngDevMode && 'Injector has already been destroyed.');
}
}
/**
* 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)) {
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$1(null);
try {
if (ngDevMode && record.value === CIRCULAR) {
throwCyclicDependencyError(stringify(token));
}
else if (record.value === NOT_YET) {
record.value = CIRCULAR;
if (ngDevMode) {
runInInjectorProfilerContext(this, 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$1(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 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 instanceof 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) {
if (injector instanceof R3Injector) {
injector.assertNotDestroyed();
}
let prevInjectorProfilerContext;
if (ngDevMode) {
prevInjectorProfilerContext = setInjectorProfilerContext({ injector, token: null });
}
const prevInjector = setCurrentInjector(injector);
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`');
}
}
var FactoryTarget;
(function (FactoryTarget) {
FactoryTarget[FactoryTarget["Directive"] = 0] = "Directive";
FactoryTarget[FactoryTarget["Component"] = 1] = "Component";
FactoryTarget[FactoryTarget["Injectable"] = 2] = "Injectable";
FactoryTarget[FactoryTarget["Pipe"] = 3] = "Pipe";
FactoryTarget[FactoryTarget["NgModule"] = 4] = "NgModule";
})(FactoryTarget || (FactoryTarget = {}));
var R3TemplateDependencyKind;
(function (R3TemplateDependencyKind) {
R3TemplateDependencyKind[R3TemplateDependencyKind["Directive"] = 0] = "Directive";
R3TemplateDependencyKind[R3TemplateDependencyKind["Pipe"] = 1] = "Pipe";
R3TemplateDependencyKind[R3TemplateDependencyKind["NgModule"] = 2] = "NgModule";
})(R3TemplateDependencyKind || (R3TemplateDependencyKind = {}));
var ViewEncapsulation;
(function (ViewEncapsulation) {
ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None";
ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom";
})(ViewEncapsulation || (ViewEncapsulation = {}));
function getCompilerFacade(request) {
const globalNg = _global['ng'];
if (globalNg && globalNg.ɵcompilerFacade) {
return globalNg.ɵcompilerFacade;
}
if (typeof ngDevMode === 'undefined' || ngDevMode) {
// Log the type as an error so that a developer can easily navigate to the type from the
// console.
console.error(`JIT compilation failed for ${request.kind}`, request.type);
let message = `The ${request.kind} '${request.type.name}' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.\n\n`;
if (request.usage === 1 /* JitCompilerUsage.PartialDeclaration */) {
message += `The ${request.kind} is part of a library that has been partially compiled.\n`;
message += `However, the Angular Linker has not processed the library such that JIT compilation is used as fallback.\n`;
message += '\n';
message += `Ideally, the library is processed using the Angular Linker to become fully AOT compiled.\n`;
}
else {
message += `JIT compilation is discouraged for production use-cases! Consider using AOT mode instead.\n`;
}
message += `Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server',\n`;
message += `or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.`;
throw new Error(message);
}
else {
throw new Error('JIT compiler unavailable');
}
}
/**
* A mapping of the @angular/core API surface used in generated expressions to the actual symbols.
*
* This should be kept up to date with the public exports of @angular/core.
*/
const angularCoreDiEnv = {
'ɵɵdefineInjectable': ɵɵdefineInjectable,
'ɵɵdefineInjector': ɵɵdefineInjector,
'ɵɵinject': ɵɵinject,
'ɵɵinvalidFactoryDep': ɵɵinvalidFactoryDep,
'resolveForwardRef': resolveForwardRef,
};
/**
* @description
*
* Represents a type that a Component or other object is instances of.
*
* An example of a `Type` is `MyCustomComponent` class, which in JavaScript is represented by
* the `MyCustomComponent` constructor function.
*
* @publicApi
*/
const Type = Function;
function isType(v) {
return typeof v === 'function';
}
/*
* #########################
* Attention: These Regular expressions have to hold even if the code is minified!
* ##########################
*/
/**
* Regular expression that detects pass-through constructors for ES5 output. This Regex
* intends to capture the common delegation pattern emitted by TypeScript and Babel. Also
* it intends to capture the pattern where existing constructors have been downleveled from
* ES2015 to ES5 using TypeScript w/ downlevel iteration. e.g.
*
* ```
* function MyClass() {
* var _this = _super.apply(this, arguments) || this;
* ```
*
* downleveled to ES5 with `downlevelIteration` for TypeScript < 4.2:
* ```
* function MyClass() {
* var _this = _super.apply(this, __spread(arguments)) || this;
* ```
*
* or downleveled to ES5 with `downlevelIteration` for TypeScript >= 4.2:
* ```
* function MyClass() {
* var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this;
* ```
*
* More details can be found in: https://github.com/angular/angular/issues/38453.
*/
const ES5_DELEGATE_CTOR = /^function\s+\S+\(\)\s*{[\s\S]+\.apply\(this,\s*(arguments|(?:[^()]+\(\[\],)?[^()]+\(arguments\).*)\)/;
/** Regular expression that detects ES2015 classes which extend from other classes. */
const ES2015_INHERITED_CLASS = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{/;
/**
* Regular expression that detects ES2015 classes which extend from other classes and
* have an explicit constructor defined.
*/
const ES2015_INHERITED_CLASS_WITH_CTOR = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(/;
/**
* Regular expression that detects ES2015 classes which extend from other classes
* and inherit a constructor.
*/
const ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{[^}]*super\(\.\.\.arguments\)/;
/**
* Determine whether a stringified type is a class which delegates its constructor
* to its parent.
*
* This is not trivial since compiled code can actually contain a constructor function
* even if the original source code did not. For instance, when the child class contains
* an initialized instance property.
*/
function isDelegateCtor(typeStr) {
return (ES5_DELEGATE_CTOR.test(typeStr) ||
ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR.test(typeStr) ||
(ES2015_INHERITED_CLASS.test(typeStr) && !ES2015_INHERITED_CLASS_WITH_CTOR.test(typeStr)));
}
class ReflectionCapabilities {
constructor(reflect) {
this._reflect = reflect || _global['Reflect'];
}
factory(t) {
return (...args) => new t(...args);
}
/** @internal */
_zipTypesAndAnnotations(paramTypes, paramAnnotations) {
let result;
if (typeof paramTypes === 'undefined') {
result = newArray(paramAnnotations.length);
}
else {
result = newArray(paramTypes.length);
}
for (let i = 0; i < result.length; i++) {
// TS outputs Object for parameters without types, while Traceur omits
// the annotations. For now we preserve the Traceur behavior to aid
// migration, but this can be revisited.
if (typeof paramTypes === 'undefined') {
result[i] = [];
}
else if (paramTypes[i] && paramTypes[i] != Object) {
result[i] = [paramTypes[i]];
}
else {
result[i] = [];
}
if (paramAnnotations && paramAnnotations[i] != null) {
result[i] = result[i].concat(paramAnnotations[i]);
}
}
return result;
}
_ownParameters(type, parentCtor) {
const typeStr = type.toString();
// If we have no decorators, we only have function.length as metadata.
// In that case, to detect whether a child class declared an own constructor or not,
// we need to look inside of that constructor to check whether it is
// just calling the parent.
// This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439
// that sets 'design:paramtypes' to []
// if a class inherits from another class but has no ctor declared itself.
if (isDelegateCtor(typeStr)) {
return null;
}
// Prefer the direct API.
if (type.parameters && type.parameters !== parentCtor.parameters) {
return type.parameters;
}
// API of tsickle for lowering decorators to properties on the class.
const tsickleCtorParams = type.ctorParameters;
if (tsickleCtorParams && tsickleCtorParams !== parentCtor.ctorParameters) {
// Newer tsickle uses a function closure
// Retain the non-function case for compatibility with older tsickle
const ctorParameters = typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams;
const paramTypes = ctorParameters.map((ctorParam) => ctorParam && ctorParam.type);
const paramAnnotations = ctorParameters.map((ctorParam) => ctorParam && convertTsickleDecoratorIntoMetadata(ctorParam.decorators));
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
// API for metadata created by invoking the decorators.
const paramAnnotations = type.hasOwnProperty(PARAMETERS) && type[PARAMETERS];
const paramTypes = this._reflect &&
this._reflect.getOwnMetadata &&
this._reflect.getOwnMetadata('design:paramtypes', type);
if (paramTypes || paramAnnotations) {
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
// If a class has no decorators, at least create metadata
// based on function.length.
// Note: We know that this is a real constructor as we checked
// the content of the constructor above.
return newArray(type.length);
}
parameters(type) {
// Note: only report metadata if we have at least one class decorator
// to stay in sync with the static reflector.
if (!isType(type)) {
return [];
}
const parentCtor = getParentCtor(type);
let parameters = this._ownParameters(type, parentCtor);
if (!parameters && parentCtor !== Object) {
parameters = this.parameters(parentCtor);
}
return parameters || [];
}
_ownAnnotations(typeOrFunc, parentCtor) {
// Prefer the direct API.
if (typeOrFunc.annotations && typeOrFunc.annotations !== parentCtor.annotations) {
let annotations = typeOrFunc.annotations;
if (typeof annotations === 'function' && annotations.annotations) {
annotations = annotations.annotations;
}
return annotations;
}
// API of tsickle for lowering decorators to properties on the class.
if (typeOrFunc.decorators && typeOrFunc.decorators !== parentCtor.decorators) {
return convertTsickleDecoratorIntoMetadata(typeOrFunc.decorators);
}
// API for metadata created by invoking the decorators.
if (typeOrFunc.hasOwnProperty(ANNOTATIONS)) {
return typeOrFunc[ANNOTATIONS];
}
return null;
}
annotations(typeOrFunc) {
if (!isType(typeOrFunc)) {
return [];
}
const parentCtor = getParentCtor(typeOrFunc);
const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
return parentAnnotations.concat(ownAnnotations);
}
_ownPropMetadata(typeOrFunc, parentCtor) {
// Prefer the direct API.
if (typeOrFunc.propMetadata &&
typeOrFunc.propMetadata !== parentCtor.propMetadata) {
let propMetadata = typeOrFunc.propMetadata;
if (typeof propMetadata === 'function' && propMetadata.propMetadata) {
propMetadata = propMetadata.propMetadata;
}
return propMetadata;
}
// API of tsickle for lowering decorators to properties on the class.
if (typeOrFunc.propDecorators &&
typeOrFunc.propDecorators !== parentCtor.propDecorators) {
const propDecorators = typeOrFunc.propDecorators;
const propMetadata = {};
Object.keys(propDecorators).forEach((prop) => {
propMetadata[prop] = convertTsickleDecoratorIntoMetadata(propDecorators[prop]);
});
return propMetadata;
}
// API for metadata created by invoking the decorators.
if (typeOrFunc.hasOwnProperty(PROP_METADATA)) {
return typeOrFunc[PROP_METADATA];
}
return null;
}
propMetadata(typeOrFunc) {
if (!isType(typeOrFunc)) {
return {};
}
const parentCtor = getParentCtor(typeOrFunc);
const propMetadata = {};
if (parentCtor !== Object) {
const parentPropMetadata = this.propMetadata(parentCtor);
Object.keys(parentPropMetadata).forEach((propName) => {
propMetadata[propName] = parentPropMetadata[propName];
});
}
const ownPropMetadata = this._ownPropMetadata(typeOrFunc, parentCtor);
if (ownPropMetadata) {
Object.keys(ownPropMetadata).forEach((propName) => {
const decorators = [];
if (propMetadata.hasOwnProperty(propName)) {
decorators.push(...propMetadata[propName]);
}
decorators.push(...ownPropMetadata[propName]);
propMetadata[propName] = decorators;
});
}
return propMetadata;
}
ownPropMetadata(typeOrFunc) {
if (!isType(typeOrFunc)) {
return {};
}
return this._ownPropMetadata(typeOrFunc, getParentCtor(typeOrFunc)) || {};
}
hasLifecycleHook(type, lcProperty) {
return type instanceof Type && lcProperty in type.prototype;
}
}
function convertTsickleDecoratorIntoMetadata(decoratorInvocations) {
if (!decoratorInvocations) {
return [];
}
return decoratorInvocations.map((decoratorInvocation) => {
const decoratorType = decoratorInvocation.type;
const annotationCls = decoratorType.annotationCls;
const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : [];
return new annotationCls(...annotationArgs);
});
}
function getParentCtor(ctor) {
const parentProto = ctor.prototype ? Object.getPrototypeOf(ctor.prototype) : null;
const parentCtor = parentProto ? parentProto.constructor : null;
// Note: We always use `Object` as the null value
// to simplify checking later on.
return parentCtor || Object;
}
// 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 REACTIVE_TEMPLATE_CONSUMER = 23;
/**
* 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 = 25;
/**
* 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;
/** Flags associated with an LContainer (saved in LContainer[FLAGS]) */
var LContainerFlags;
(function (LContainerFlags) {
LContainerFlags[LContainerFlags["None"] = 0] = "None";
/**
* Flag to signify that this `LContainer` may have transplanted views which need to be change
* detected. (see: `LView[DECLARATION_COMPONENT_VIEW])`.
*
* This flag, once set, is never unset for the `LContainer`.
*/
LContainerFlags[LContainerFlags["HasTransplantedViews"] = 2] = "HasTransplantedViews";
})(LContainerFlags || (LContainerFlags = {}));
/**
* 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) {
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) {
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 assertCurrentTNodeIsParent(isParent) {
assertEqual(isParent, true, 'currentTNode should be a parent');
}
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, errMessage || '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, errMessage ||
'Components with projection nodes () must have projection slots defined.');
}
function assertParentView(lView, errMessage) {
assertDefined(lView, errMessage || "Component views should always have a parent view (component's host view)");
}
function assertNoDuplicateDirectives(directives) {
// The array needs at least two elements in order to have duplicates.
if (directives.length < 2) {
return;
}
const seenDirectives = new Set();
for (const current of directives) {
if (seenDirectives.has(current)) {
throw new RuntimeError(309 /* RuntimeErrorCode.DUPLICATE_DIRECTIVE */, `Directive ${current.type.name} matches multiple times on the same element. ` +
`Directives can only match an element once.`);
}
seenDirectives.add(current);
}
}
/**
* 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');
}
/**
* Represents a basic change from a previous to a new value for a single
* property on a directive instance. Passed as a value in a
* {@link SimpleChanges} object to the `ngOnChanges` hook.
*
* @see {@link OnChanges}
*
* @publicApi
*/
class SimpleChange {
constructor(previousValue, currentValue, firstChange) {
this.previousValue = previousValue;
this.currentValue = currentValue;
this.firstChange = firstChange;
}
/**
* Check whether the new value is the first value assigned.
*/
isFirstChange() {
return this.firstChange;
}
}
function applyValueToInputField(instance, inputSignalNode, privateName, value) {
if (inputSignalNode !== null) {
inputSignalNode.applyValueToInputSignal(inputSignalNode, value);
}
else {
instance[privateName] = value;
}
}
/**
* The NgOnChangesFeature decorates a component with support for the ngOnChanges
* lifecycle hook, so it should be included in any component that implements
* that hook.
*
* If the component or directive uses inheritance, the NgOnChangesFeature MUST
* be included as a feature AFTER {@link InheritDefinitionFeature}, otherwise
* inherited properties will not be propagated to the ngOnChanges lifecycle
* hook.
*
* Example usage:
*
* ```
* static ɵcmp = defineComponent({
* ...
* inputs: {name: 'publicName'},
* features: [NgOnChangesFeature]
* });
* ```
*
* @codeGenApi
*/
function ɵɵNgOnChangesFeature() {
return NgOnChangesFeatureImpl;
}
function NgOnChangesFeatureImpl(definition) {
if (definition.type.prototype.ngOnChanges) {
definition.setInput = ngOnChangesSetInput;
}
return rememberChangeHistoryAndInvokeOnChangesHook;
}
// This option ensures that the ngOnChanges lifecycle hook will be inherited
// from superclasses (in InheritDefinitionFeature).
/** @nocollapse */
// tslint:disable-next-line:no-toplevel-property-access
ɵɵNgOnChangesFeature.ngInherit = true;
/**
* This is a synthetic lifecycle hook which gets inserted into `TView.preOrderHooks` to simulate
* `ngOnChanges`.
*
* The hook reads the `NgSimpleChangesStore` data from the component instance and if changes are
* found it invokes `ngOnChanges` on the component instance.
*
* @param this Component instance. Because this function gets inserted into `TView.preOrderHooks`,
* it is guaranteed to be called with component instance.
*/
function rememberChangeHistoryAndInvokeOnChangesHook() {
const simpleChangesStore = getSimpleChangesStore(this);
const current = simpleChangesStore?.current;
if (current) {
const previous = simpleChangesStore.previous;
if (previous === EMPTY_OBJ) {
simpleChangesStore.previous = current;
}
else {
// New changes are copied to the previous store, so that we don't lose history for inputs
// which were not changed this time
for (let key in current) {
previous[key] = current[key];
}
}
simpleChangesStore.current = null;
this.ngOnChanges(current);
}
}
function ngOnChangesSetInput(instance, inputSignalNode, value, publicName, privateName) {
const declaredName = this.declaredInputs[publicName];
ngDevMode && assertString(declaredName, 'Name of input in ngOnChanges has to be a string');
const simpleChangesStore = getSimpleChangesStore(instance) ||
setSimpleChangesStore(instance, { previous: EMPTY_OBJ, current: null });
const current = simpleChangesStore.current || (simpleChangesStore.current = {});
const previous = simpleChangesStore.previous;
const previousChange = previous[declaredName];
current[declaredName] = new SimpleChange(previousChange && previousChange.currentValue, value, previous === EMPTY_OBJ);
applyValueToInputField(instance, inputSignalNode, privateName, value);
}
const SIMPLE_CHANGES_STORE = '__ngSimpleChanges__';
function getSimpleChangesStore(instance) {
return instance[SIMPLE_CHANGES_STORE] || null;
}
function setSimpleChangesStore(instance, store) {
return (instance[SIMPLE_CHANGES_STORE] = store);
}
let profilerCallback = null;
/**
* Sets the callback function which will be invoked before and after performing certain actions at
* runtime (for example, before and after running change detection).
*
* 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.
*/
const setProfiler = (profiler) => {
profilerCallback = profiler;
};
/**
* Profiler function which wraps user code executed by the runtime.
*
* @param event ProfilerEvent corresponding to the execution context
* @param instance component instance
* @param hookOrListener lifecycle hook function or output listener. The value depends on the
* execution context
* @returns
*/
const profiler = function (event, instance, hookOrListener) {
if (profilerCallback != null /* both `null` and `undefined` */) {
profilerCallback(event, instance, hookOrListener);
}
};
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];
}
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 ((lView[FLAGS] & 256 /* LViewFlags.Destroyed */) === 256 /* LViewFlags.Destroyed */) {
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;
}
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;
/**
* Returns true if the instruction state stack is empty.
*
* Intended to be called from tests only (tree shaken otherwise).
*/
function specOnlyIsInstructionStateEmpty() {
return instructionState.lFrame.parent === null;
}
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$1() {
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:
* ```
*
* 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:
* ```
*
* 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) {
_isRefreshingViews = mode;
}
// 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 & InjectFlags.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 & InjectFlags.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$1() {
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;
}
/**
* Adds all directive lifecycle hooks from the given `DirectiveDef` to the given `TView`.
*
* Must be run *only* on the first template pass.
*
* Sets up the pre-order hooks on the provided `tView`,
* see {@link HookData} for details about the data structure.
*
* @param directiveIndex The index of the directive in LView
* @param directiveDef The definition containing the hooks to setup in tView
* @param tView The current TView
*/
function registerPreOrderHooks(directiveIndex, directiveDef, tView) {
ngDevMode && assertFirstCreatePass(tView);
const { ngOnChanges, ngOnInit, ngDoCheck } = directiveDef.type.prototype;
if (ngOnChanges) {
const wrappedOnChanges = NgOnChangesFeatureImpl(directiveDef);
(tView.preOrderHooks ??= []).push(directiveIndex, wrappedOnChanges);
(tView.preOrderCheckHooks ??= []).push(directiveIndex, wrappedOnChanges);
}
if (ngOnInit) {
(tView.preOrderHooks ??= []).push(0 - directiveIndex, ngOnInit);
}
if (ngDoCheck) {
(tView.preOrderHooks ??= []).push(directiveIndex, ngDoCheck);
(tView.preOrderCheckHooks ??= []).push(directiveIndex, ngDoCheck);
}
}
/**
*
* Loops through the directives on the provided `tNode` and queues hooks to be
* run that are not initialization hooks.
*
* Should be executed during `elementEnd()` and similar to
* preserve hook execution order. Content, view, and destroy hooks for projected
* components and directives must be called *before* their hosts.
*
* Sets up the content, view, and destroy hooks on the provided `tView`,
* see {@link HookData} for details about the data structure.
*
* NOTE: This does not set up `onChanges`, `onInit` or `doCheck`, those are set up
* separately at `elementStart`.
*
* @param tView The current TView
* @param tNode The TNode whose directives are to be searched for hooks to queue
*/
function registerPostOrderHooks(tView, tNode) {
ngDevMode && assertFirstCreatePass(tView);
// It's necessary to loop through the directives at elementEnd() (rather than processing in
// directiveCreate) so we can preserve the current hook order. Content, view, and destroy
// hooks for projected components and directives must be called *before* their hosts.
for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) {
const directiveDef = tView.data[i];
ngDevMode && assertDefined(directiveDef, 'Expecting DirectiveDef');
const lifecycleHooks = directiveDef.type.prototype;
const { ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked, ngOnDestroy, } = lifecycleHooks;
if (ngAfterContentInit) {
(tView.contentHooks ??= []).push(-i, ngAfterContentInit);
}
if (ngAfterContentChecked) {
(tView.contentHooks ??= []).push(i, ngAfterContentChecked);
(tView.contentCheckHooks ??= []).push(i, ngAfterContentChecked);
}
if (ngAfterViewInit) {
(tView.viewHooks ??= []).push(-i, ngAfterViewInit);
}
if (ngAfterViewChecked) {
(tView.viewHooks ??= []).push(i, ngAfterViewChecked);
(tView.viewCheckHooks ??= []).push(i, ngAfterViewChecked);
}
if (ngOnDestroy != null) {
(tView.destroyHooks ??= []).push(i, ngOnDestroy);
}
}
}
/**
* Executing hooks requires complex logic as we need to deal with 2 constraints.
*
* 1. Init hooks (ngOnInit, ngAfterContentInit, ngAfterViewInit) must all be executed once and only
* once, across many change detection cycles. This must be true even if some hooks throw, or if
* some recursively trigger a change detection cycle.
* To solve that, it is required to track the state of the execution of these init hooks.
* This is done by storing and maintaining flags in the view: the {@link InitPhaseState},
* and the index within that phase. They can be seen as a cursor in the following structure:
* [[onInit1, onInit2], [afterContentInit1], [afterViewInit1, afterViewInit2, afterViewInit3]]
* They are stored as flags in LView[FLAGS].
*
* 2. Pre-order hooks can be executed in batches, because of the select instruction.
* To be able to pause and resume their execution, we also need some state about the hook's array
* that is being processed:
* - the index of the next hook to be executed
* - the number of init hooks already found in the processed part of the array
* They are stored as flags in LView[PREORDER_HOOK_FLAGS].
*/
/**
* Executes pre-order check hooks ( OnChanges, DoChanges) given a view where all the init hooks were
* executed once. This is a light version of executeInitAndCheckPreOrderHooks where we can skip read
* / write of the init-hooks related flags.
* @param lView The LView where hooks are defined
* @param hooks Hooks to be run
* @param nodeIndex 3 cases depending on the value:
* - undefined: all hooks from the array should be executed (post-order case)
* - null: execute hooks only from the saved index until the end of the array (pre-order case, when
* flushing the remaining hooks)
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
* case, when executing select(number))
*/
function executeCheckHooks(lView, hooks, nodeIndex) {
callHooks(lView, hooks, 3 /* InitPhaseState.InitPhaseCompleted */, nodeIndex);
}
/**
* Executes post-order init and check hooks (one of AfterContentInit, AfterContentChecked,
* AfterViewInit, AfterViewChecked) given a view where there are pending init hooks to be executed.
* @param lView The LView where hooks are defined
* @param hooks Hooks to be run
* @param initPhase A phase for which hooks should be run
* @param nodeIndex 3 cases depending on the value:
* - undefined: all hooks from the array should be executed (post-order case)
* - null: execute hooks only from the saved index until the end of the array (pre-order case, when
* flushing the remaining hooks)
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
* case, when executing select(number))
*/
function executeInitAndCheckHooks(lView, hooks, initPhase, nodeIndex) {
ngDevMode &&
assertNotEqual(initPhase, 3 /* InitPhaseState.InitPhaseCompleted */, 'Init pre-order hooks should not be called more than once');
if ((lView[FLAGS] & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) {
callHooks(lView, hooks, initPhase, nodeIndex);
}
}
function incrementInitPhaseFlags(lView, initPhase) {
ngDevMode &&
assertNotEqual(initPhase, 3 /* InitPhaseState.InitPhaseCompleted */, 'Init hooks phase should not be incremented after all init hooks have been run.');
let flags = lView[FLAGS];
if ((flags & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) {
flags &= 16383 /* LViewFlags.IndexWithinInitPhaseReset */;
flags += 1 /* LViewFlags.InitPhaseStateIncrementer */;
lView[FLAGS] = flags;
}
}
/**
* Calls lifecycle hooks with their contexts, skipping init hooks if it's not
* the first LView pass
*
* @param currentView The current view
* @param arr The array in which the hooks are found
* @param initPhaseState the current state of the init phase
* @param currentNodeIndex 3 cases depending on the value:
* - undefined: all hooks from the array should be executed (post-order case)
* - null: execute hooks only from the saved index until the end of the array (pre-order case, when
* flushing the remaining hooks)
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
* case, when executing select(number))
*/
function callHooks(currentView, arr, initPhase, currentNodeIndex) {
ngDevMode &&
assertEqual(isInCheckNoChangesMode(), false, 'Hooks should never be run when in check no changes mode.');
const startIndex = currentNodeIndex !== undefined
? currentView[PREORDER_HOOK_FLAGS] & 65535 /* PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask */
: 0;
const nodeIndexLimit = currentNodeIndex != null ? currentNodeIndex : -1;
const max = arr.length - 1; // Stop the loop at length - 1, because we look for the hook at i + 1
let lastNodeIndexFound = 0;
for (let i = startIndex; i < max; i++) {
const hook = arr[i + 1];
if (typeof hook === 'number') {
lastNodeIndexFound = arr[i];
if (currentNodeIndex != null && lastNodeIndexFound >= currentNodeIndex) {
break;
}
}
else {
const isInitHook = arr[i] < 0;
if (isInitHook) {
currentView[PREORDER_HOOK_FLAGS] += 65536 /* PreOrderHookFlags.NumberOfInitHooksCalledIncrementer */;
}
if (lastNodeIndexFound < nodeIndexLimit || nodeIndexLimit == -1) {
callHook(currentView, initPhase, arr, i);
currentView[PREORDER_HOOK_FLAGS] =
(currentView[PREORDER_HOOK_FLAGS] & 4294901760 /* PreOrderHookFlags.NumberOfInitHooksCalledMask */) +
i +
2;
}
i++;
}
}
}
/**
* Executes a single lifecycle hook, making sure that:
* - it is called in the non-reactive context;
* - profiling data are registered.
*/
function callHookInternal(directive, hook) {
profiler(4 /* ProfilerEvent.LifecycleHookStart */, directive, hook);
const prevConsumer = setActiveConsumer$1(null);
try {
hook.call(directive);
}
finally {
setActiveConsumer$1(prevConsumer);
profiler(5 /* ProfilerEvent.LifecycleHookEnd */, directive, hook);
}
}
/**
* Execute one hook against the current `LView`.
*
* @param currentView The current view
* @param initPhaseState the current state of the init phase
* @param arr The array in which the hooks are found
* @param i The current index within the hook data array
*/
function callHook(currentView, initPhase, arr, i) {
const isInitHook = arr[i] < 0;
const hook = arr[i + 1];
const directiveIndex = isInitHook ? -arr[i] : arr[i];
const directive = currentView[directiveIndex];
if (isInitHook) {
const indexWithintInitPhase = currentView[FLAGS] >> 14 /* LViewFlags.IndexWithinInitPhaseShift */;
// The init phase state must be always checked here as it may have been recursively updated.
if (indexWithintInitPhase <
currentView[PREORDER_HOOK_FLAGS] >> 16 /* PreOrderHookFlags.NumberOfInitHooksCalledShift */ &&
(currentView[FLAGS] & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) {
currentView[FLAGS] += 16384 /* LViewFlags.IndexWithinInitPhaseIncrementer */;
callHookInternal(directive, hook);
}
}
else {
callHookInternal(directive, hook);
}
}
const NO_PARENT_INJECTOR = -1;
/**
* Each injector is saved in 9 contiguous slots in `LView` and 9 contiguous slots in
* `TView.data`. This allows us to store information about the current node's tokens (which
* can be shared in `TView`) as well as the tokens of its ancestor nodes (which cannot be
* shared, so they live in `LView`).
*
* Each of these slots (aside from the last slot) contains a bloom filter. This bloom filter
* determines whether a directive is available on the associated node or not. This prevents us
* from searching the directives array at this level unless it's probable the directive is in it.
*
* See: https://en.wikipedia.org/wiki/Bloom_filter for more about bloom filters.
*
* Because all injectors have been flattened into `LView` and `TViewData`, they cannot typed
* using interfaces as they were previously. The start index of each `LInjector` and `TInjector`
* will differ based on where it is flattened into the main array, so it's not possible to know
* the indices ahead of time and save their types here. The interfaces are still included here
* for documentation purposes.
*
* export interface LInjector extends Array {
*
* // Cumulative bloom for directive IDs 0-31 (IDs are % BLOOM_SIZE)
* [0]: number;
*
* // Cumulative bloom for directive IDs 32-63
* [1]: number;
*
* // Cumulative bloom for directive IDs 64-95
* [2]: number;
*
* // Cumulative bloom for directive IDs 96-127
* [3]: number;
*
* // Cumulative bloom for directive IDs 128-159
* [4]: number;
*
* // Cumulative bloom for directive IDs 160 - 191
* [5]: number;
*
* // Cumulative bloom for directive IDs 192 - 223
* [6]: number;
*
* // Cumulative bloom for directive IDs 224 - 255
* [7]: number;
*
* // We need to store a reference to the injector's parent so DI can keep looking up
* // the injector tree until it finds the dependency it's looking for.
* [PARENT_INJECTOR]: number;
* }
*
* export interface TInjector extends Array {
*
* // Shared node bloom for directive IDs 0-31 (IDs are % BLOOM_SIZE)
* [0]: number;
*
* // Shared node bloom for directive IDs 32-63
* [1]: number;
*
* // Shared node bloom for directive IDs 64-95
* [2]: number;
*
* // Shared node bloom for directive IDs 96-127
* [3]: number;
*
* // Shared node bloom for directive IDs 128-159
* [4]: number;
*
* // Shared node bloom for directive IDs 160 - 191
* [5]: number;
*
* // Shared node bloom for directive IDs 192 - 223
* [6]: number;
*
* // Shared node bloom for directive IDs 224 - 255
* [7]: number;
*
* // Necessary to find directive indices for a particular node.
* [TNODE]: TElementNode|TElementContainerNode|TContainerNode;
* }
*/
/**
* Factory for creating instances of injectors in the NodeInjector.
*
* This factory is complicated by the fact that it can resolve `multi` factories as well.
*
* NOTE: Some of the fields are optional which means that this class has two hidden classes.
* - One without `multi` support (most common)
* - One with `multi` values, (rare).
*
* Since VMs can cache up to 4 inline hidden classes this is OK.
*
* - Single factory: Only `resolving` and `factory` is defined.
* - `providers` factory: `componentProviders` is a number and `index = -1`.
* - `viewProviders` factory: `componentProviders` is a number and `index` points to `providers`.
*/
class NodeInjectorFactory {
constructor(
/**
* Factory to invoke in order to create a new instance.
*/
factory,
/**
* Set to `true` if the token is declared in `viewProviders` (or if it is component).
*/
isViewProvider, injectImplementation) {
this.factory = factory;
/**
* Marker set to true during factory invocation to see if we get into recursive loop.
* Recursive loop causes an error to be displayed.
*/
this.resolving = false;
ngDevMode && assertDefined(factory, 'Factory not specified');
ngDevMode && assertEqual(typeof factory, 'function', 'Expected factory function.');
this.canSeeViewProviders = isViewProvider;
this.injectImpl = injectImplementation;
}
}
function isFactory(obj) {
return obj instanceof NodeInjectorFactory;
}
/**
* Converts `TNodeType` into human readable text.
* Make sure this matches with `TNodeType`
*/
function toTNodeTypeAsString(tNodeType) {
let text = '';
tNodeType & 1 /* TNodeType.Text */ && (text += '|Text');
tNodeType & 2 /* TNodeType.Element */ && (text += '|Element');
tNodeType & 4 /* TNodeType.Container */ && (text += '|Container');
tNodeType & 8 /* TNodeType.ElementContainer */ && (text += '|ElementContainer');
tNodeType & 16 /* TNodeType.Projection */ && (text += '|Projection');
tNodeType & 32 /* TNodeType.Icu */ && (text += '|IcuContainer');
tNodeType & 64 /* TNodeType.Placeholder */ && (text += '|Placeholder');
tNodeType & 128 /* TNodeType.LetDeclaration */ && (text += '|LetDeclaration');
return text.length > 0 ? text.substring(1) : text;
}
/**
* Helper function to detect if a given value matches a `TNode` shape.
*
* The logic uses the `insertBeforeIndex` and its possible values as
* a way to differentiate a TNode shape from other types of objects
* within the `TView.data`. This is not a perfect check, but it can
* be a reasonable differentiator, since we control the shapes of objects
* within `TView.data`.
*/
function isTNodeShape(value) {
return (value != null &&
typeof value === 'object' &&
(value.insertBeforeIndex === null ||
typeof value.insertBeforeIndex === 'number' ||
Array.isArray(value.insertBeforeIndex)));
}
function isLetDeclaration(tNode) {
return !!(tNode.type & 128 /* TNodeType.LetDeclaration */);
}
/**
* Returns `true` if the `TNode` has a directive which has `@Input()` for `class` binding.
*
* ```
*
* ```
* and
* ```
* @Directive({
* })
* class MyDirective {
* @Input()
* class: string;
* }
* ```
*
* In the above case it is necessary to write the reconciled styling information into the
* directive's input.
*
* @param tNode
*/
function hasClassInput(tNode) {
return (tNode.flags & 8 /* TNodeFlags.hasClassInput */) !== 0;
}
/**
* Returns `true` if the `TNode` has a directive which has `@Input()` for `style` binding.
*
* ```
*
* ```
* and
* ```
* @Directive({
* })
* class MyDirective {
* @Input()
* class: string;
* }
* ```
*
* In the above case it is necessary to write the reconciled styling information into the
* directive's input.
*
* @param tNode
*/
function hasStyleInput(tNode) {
return (tNode.flags & 16 /* TNodeFlags.hasStyleInput */) !== 0;
}
function assertTNodeType(tNode, expectedTypes, message) {
assertDefined(tNode, 'should be called with a TNode');
if ((tNode.type & expectedTypes) === 0) {
throwError(message ||
`Expected [${toTNodeTypeAsString(expectedTypes)}] but got ${toTNodeTypeAsString(tNode.type)}.`);
}
}
function assertPureTNodeType(type) {
if (!(type === 2 /* TNodeType.Element */ ||
type === 1 /* TNodeType.Text */ ||
type === 4 /* TNodeType.Container */ ||
type === 8 /* TNodeType.ElementContainer */ ||
type === 32 /* TNodeType.Icu */ ||
type === 16 /* TNodeType.Projection */ ||
type === 64 /* TNodeType.Placeholder */ ||
type === 128 /* TNodeType.LetDeclaration */)) {
throwError(`Expected TNodeType to have only a single type selected, but got ${toTNodeTypeAsString(type)}.`);
}
}
// This default value is when checking the hierarchy for a token.
//
// It means both:
// - the token is not provided by the current injector,
// - only the element injectors should be checked (ie do not check module injectors
//
// mod1
// /
// el1 mod2
// \ /
// el2
//
// When requesting el2.injector.get(token), we should check in the following order and return the
// first found value:
// - el2.injector.get(token, default)
// - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module
// - mod2.injector.get(token, default)
const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
/**
* Injector that looks up a value using a specific injector, before falling back to the module
* injector. Used primarily when creating components or embedded views dynamically.
*/
class ChainedInjector {
constructor(injector, parentInjector) {
this.injector = injector;
this.parentInjector = parentInjector;
}
get(token, notFoundValue, flags) {
flags = convertToBitFlags(flags);
const value = this.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR, flags);
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
// Return the value from the root element injector when
// - it provides it
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
// - the module injector should not be checked
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
return value;
}
return this.parentInjector.get(token, notFoundValue, flags);
}
}
/// Parent Injector Utils ///////////////////////////////////////////////////////////////
function hasParentInjector(parentLocation) {
return parentLocation !== NO_PARENT_INJECTOR;
}
function getParentInjectorIndex(parentLocation) {
if (ngDevMode) {
assertNumber(parentLocation, 'Number expected');
assertNotEqual(parentLocation, -1, 'Not a valid state.');
const parentInjectorIndex = parentLocation & 32767 /* RelativeInjectorLocationFlags.InjectorIndexMask */;
assertGreaterThan(parentInjectorIndex, HEADER_OFFSET, 'Parent injector must be pointing past HEADER_OFFSET.');
}
return parentLocation & 32767 /* RelativeInjectorLocationFlags.InjectorIndexMask */;
}
function getParentInjectorViewOffset(parentLocation) {
return parentLocation >> 16 /* RelativeInjectorLocationFlags.ViewOffsetShift */;
}
/**
* Unwraps a parent injector location number to find the view offset from the current injector,
* then walks up the declaration view tree until the view is found that contains the parent
* injector.
*
* @param location The location of the parent injector, which contains the view offset
* @param startView The LView instance from which to start walking up the view tree
* @returns The LView instance that contains the parent injector
*/
function getParentInjectorView(location, startView) {
let viewOffset = getParentInjectorViewOffset(location);
let parentView = startView;
// For most cases, the parent injector can be found on the host node (e.g. for component
// or container), but we must keep the loop here to support the rarer case of deeply nested
// tags or inline views, where the parent injector might live many views
// above the child injector.
while (viewOffset > 0) {
parentView = parentView[DECLARATION_VIEW];
viewOffset--;
}
return parentView;
}
/**
* Detects whether an injector is an instance of a `ChainedInjector`,
* created based on the `OutletInjector`.
*/
function isRouterOutletInjector(currentInjector) {
return (currentInjector instanceof ChainedInjector &&
typeof currentInjector.injector.__ngOutletInjector === 'function');
}
/**
* Defines if the call to `inject` should include `viewProviders` in its resolution.
*
* This is set to true when we try to instantiate a component. This value is reset in
* `getNodeInjectable` to a value which matches the declaration location of the token about to be
* instantiated. This is done so that if we are injecting a token which was declared outside of
* `viewProviders` we don't accidentally pull `viewProviders` in.
*
* Example:
*
* ```
* @Injectable()
* class MyService {
* constructor(public value: String) {}
* }
*
* @Component({
* providers: [
* MyService,
* {provide: String, value: 'providers' }
* ]
* viewProviders: [
* {provide: String, value: 'viewProviders'}
* ]
* })
* class MyComponent {
* constructor(myService: MyService, value: String) {
* // We expect that Component can see into `viewProviders`.
* expect(value).toEqual('viewProviders');
* // `MyService` was not declared in `viewProviders` hence it can't see it.
* expect(myService.value).toEqual('providers');
* }
* }
*
* ```
*/
let includeViewProviders = true;
function setIncludeViewProviders(v) {
const oldValue = includeViewProviders;
includeViewProviders = v;
return oldValue;
}
/**
* The number of slots in each bloom filter (used by DI). The larger this number, the fewer
* directives that will share slots, and thus, the fewer false positives when checking for
* the existence of a directive.
*/
const BLOOM_SIZE = 256;
const BLOOM_MASK = BLOOM_SIZE - 1;
/**
* The number of bits that is represented by a single bloom bucket. JS bit operations are 32 bits,
* so each bucket represents 32 distinct tokens which accounts for log2(32) = 5 bits of a bloom hash
* number.
*/
const BLOOM_BUCKET_BITS = 5;
/** Counter used to generate unique IDs for directives. */
let nextNgElementId = 0;
/** Value used when something wasn't found by an injector. */
const NOT_FOUND = {};
/**
* Registers this directive as present in its node's injector by flipping the directive's
* corresponding bit in the injector's bloom filter.
*
* @param injectorIndex The index of the node injector where this token should be registered
* @param tView The TView for the injector's bloom filters
* @param type The directive token to register
*/
function bloomAdd(injectorIndex, tView, type) {
ngDevMode && assertEqual(tView.firstCreatePass, true, 'expected firstCreatePass to be true');
let id;
if (typeof type === 'string') {
id = type.charCodeAt(0) || 0;
}
else if (type.hasOwnProperty(NG_ELEMENT_ID)) {
id = type[NG_ELEMENT_ID];
}
// Set a unique ID on the directive type, so if something tries to inject the directive,
// we can easily retrieve the ID and hash it into the bloom bit that should be checked.
if (id == null) {
id = type[NG_ELEMENT_ID] = nextNgElementId++;
}
// We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each),
// so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter.
const bloomHash = id & BLOOM_MASK;
// Create a mask that targets the specific bit associated with the directive.
// JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
// to bit positions 0 - 31 in a 32 bit integer.
const mask = 1 << bloomHash;
// Each bloom bucket in `tData` represents `BLOOM_BUCKET_BITS` number of bits of `bloomHash`.
// Any bits in `bloomHash` beyond `BLOOM_BUCKET_BITS` indicate the bucket offset that the mask
// should be written to.
tView.data[injectorIndex + (bloomHash >> BLOOM_BUCKET_BITS)] |= mask;
}
/**
* Creates (or gets an existing) injector for a given element or container.
*
* @param tNode for which an injector should be retrieved / created.
* @param lView View where the node is stored
* @returns Node injector
*/
function getOrCreateNodeInjectorForNode(tNode, lView) {
const existingInjectorIndex = getInjectorIndex(tNode, lView);
if (existingInjectorIndex !== -1) {
return existingInjectorIndex;
}
const tView = lView[TVIEW];
if (tView.firstCreatePass) {
tNode.injectorIndex = lView.length;
insertBloom(tView.data, tNode); // foundation for node bloom
insertBloom(lView, null); // foundation for cumulative bloom
insertBloom(tView.blueprint, null);
}
const parentLoc = getParentInjectorLocation(tNode, lView);
const injectorIndex = tNode.injectorIndex;
// If a parent injector can't be found, its location is set to -1.
// In that case, we don't need to set up a cumulative bloom
if (hasParentInjector(parentLoc)) {
const parentIndex = getParentInjectorIndex(parentLoc);
const parentLView = getParentInjectorView(parentLoc, lView);
const parentData = parentLView[TVIEW].data;
// Creates a cumulative bloom filter that merges the parent's bloom filter
// and its own cumulative bloom (which contains tokens for all ancestors)
for (let i = 0; i < 8 /* NodeInjectorOffset.BLOOM_SIZE */; i++) {
lView[injectorIndex + i] = parentLView[parentIndex + i] | parentData[parentIndex + i];
}
}
lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */] = parentLoc;
return injectorIndex;
}
function insertBloom(arr, footer) {
arr.push(0, 0, 0, 0, 0, 0, 0, 0, footer);
}
function getInjectorIndex(tNode, lView) {
if (tNode.injectorIndex === -1 ||
// If the injector index is the same as its parent's injector index, then the index has been
// copied down from the parent node. No injector has been created yet on this node.
(tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) ||
// After the first template pass, the injector index might exist but the parent values
// might not have been calculated yet for this instance
lView[tNode.injectorIndex + 8 /* NodeInjectorOffset.PARENT */] === null) {
return -1;
}
else {
ngDevMode && assertIndexInRange(lView, tNode.injectorIndex);
return tNode.injectorIndex;
}
}
/**
* Finds the index of the parent injector, with a view offset if applicable. Used to set the
* parent injector initially.
*
* @returns Returns a number that is the combination of the number of LViews that we have to go up
* to find the LView containing the parent inject AND the index of the injector within that LView.
*/
function getParentInjectorLocation(tNode, lView) {
if (tNode.parent && tNode.parent.injectorIndex !== -1) {
// If we have a parent `TNode` and there is an injector associated with it we are done, because
// the parent injector is within the current `LView`.
return tNode.parent.injectorIndex; // ViewOffset is 0
}
// When parent injector location is computed it may be outside of the current view. (ie it could
// be pointing to a declared parent location). This variable stores number of declaration parents
// we need to walk up in order to find the parent injector location.
let declarationViewOffset = 0;
let parentTNode = null;
let lViewCursor = lView;
// The parent injector is not in the current `LView`. We will have to walk the declared parent
// `LView` hierarchy and look for it. If we walk of the top, that means that there is no parent
// `NodeInjector`.
while (lViewCursor !== null) {
parentTNode = getTNodeFromLView(lViewCursor);
if (parentTNode === null) {
// If we have no parent, than we are done.
return NO_PARENT_INJECTOR;
}
ngDevMode && parentTNode && assertTNodeForLView(parentTNode, lViewCursor[DECLARATION_VIEW]);
// Every iteration of the loop requires that we go to the declared parent.
declarationViewOffset++;
lViewCursor = lViewCursor[DECLARATION_VIEW];
if (parentTNode.injectorIndex !== -1) {
// We found a NodeInjector which points to something.
return (parentTNode.injectorIndex |
(declarationViewOffset <<
16 /* RelativeInjectorLocationFlags.ViewOffsetShift */));
}
}
return NO_PARENT_INJECTOR;
}
/**
* Makes a type or an injection token public to the DI system by adding it to an
* injector's bloom filter.
*
* @param di The node injector in which a directive will be added
* @param token The type or the injection token to be made public
*/
function diPublicInInjector(injectorIndex, tView, token) {
bloomAdd(injectorIndex, tView, token);
}
/**
* Inject static attribute value into directive constructor.
*
* This method is used with `factory` functions which are generated as part of
* `defineDirective` or `defineComponent`. The method retrieves the static value
* of an attribute. (Dynamic attributes are not supported since they are not resolved
* at the time of injection and can change over time.)
*
* # Example
* Given:
* ```
* @Component(...)
* class MyComponent {
* constructor(@Attribute('title') title: string) { ... }
* }
* ```
* When instantiated with
* ```
*
* ```
*
* Then factory method generated is:
* ```
* MyComponent.ɵcmp = defineComponent({
* factory: () => new MyComponent(injectAttribute('title'))
* ...
* })
* ```
*
* @publicApi
*/
function injectAttributeImpl(tNode, attrNameToInject) {
ngDevMode && assertTNodeType(tNode, 12 /* TNodeType.AnyContainer */ | 3 /* TNodeType.AnyRNode */);
ngDevMode && assertDefined(tNode, 'expecting tNode');
if (attrNameToInject === 'class') {
return tNode.classes;
}
if (attrNameToInject === 'style') {
return tNode.styles;
}
const attrs = tNode.attrs;
if (attrs) {
const attrsLength = attrs.length;
let i = 0;
while (i < attrsLength) {
const value = attrs[i];
// If we hit a `Bindings` or `Template` marker then we are done.
if (isNameOnlyAttributeMarker(value))
break;
// Skip namespaced attributes
if (value === 0 /* AttributeMarker.NamespaceURI */) {
// we skip the next two values
// as namespaced attributes looks like
// [..., AttributeMarker.NamespaceURI, 'http://someuri.com/test', 'test:exist',
// 'existValue', ...]
i = i + 2;
}
else if (typeof value === 'number') {
// Skip to the first value of the marked attribute.
i++;
while (i < attrsLength && typeof attrs[i] === 'string') {
i++;
}
}
else if (value === attrNameToInject) {
return attrs[i + 1];
}
else {
i = i + 2;
}
}
}
return null;
}
function notFoundValueOrThrow(notFoundValue, token, flags) {
if (flags & InjectFlags.Optional || notFoundValue !== undefined) {
return notFoundValue;
}
else {
throwProviderNotFoundError(token, 'NodeInjector');
}
}
/**
* Returns the value associated to the given token from the ModuleInjector or throws exception
*
* @param lView The `LView` that contains the `tNode`
* @param token The token to look for
* @param flags Injection flags
* @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
* @returns the value from the injector or throws an exception
*/
function lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue) {
if (flags & InjectFlags.Optional && notFoundValue === undefined) {
// This must be set or the NullInjector will throw for optional deps
notFoundValue = null;
}
if ((flags & (InjectFlags.Self | InjectFlags.Host)) === 0) {
const moduleInjector = lView[INJECTOR];
// switch to `injectInjectorOnly` implementation for module injector, since module injector
// should not have access to Component/Directive DI scope (that may happen through
// `directiveInject` implementation)
const previousInjectImplementation = setInjectImplementation(undefined);
try {
if (moduleInjector) {
return moduleInjector.get(token, notFoundValue, flags & InjectFlags.Optional);
}
else {
return injectRootLimpMode(token, notFoundValue, flags & InjectFlags.Optional);
}
}
finally {
setInjectImplementation(previousInjectImplementation);
}
}
return notFoundValueOrThrow(notFoundValue, token, flags);
}
/**
* Returns the value associated to the given token from the NodeInjectors => ModuleInjector.
*
* Look for the injector providing the token by walking up the node injector tree and then
* the module injector tree.
*
* This function patches `token` with `__NG_ELEMENT_ID__` which contains the id for the bloom
* filter. `-1` is reserved for injecting `Injector` (implemented by `NodeInjector`)
*
* @param tNode The Node where the search for the injector should start
* @param lView The `LView` that contains the `tNode`
* @param token The token to look for
* @param flags Injection flags
* @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
*/
function getOrCreateInjectable(tNode, lView, token, flags = InjectFlags.Default, notFoundValue) {
if (tNode !== null) {
// If the view or any of its ancestors have an embedded
// view injector, we have to look it up there first.
if (lView[FLAGS] & 2048 /* LViewFlags.HasEmbeddedViewInjector */ &&
// The token must be present on the current node injector when the `Self`
// flag is set, so the lookup on embedded view injector(s) can be skipped.
!(flags & InjectFlags.Self)) {
const embeddedInjectorValue = lookupTokenUsingEmbeddedInjector(tNode, lView, token, flags, NOT_FOUND);
if (embeddedInjectorValue !== NOT_FOUND) {
return embeddedInjectorValue;
}
}
// Otherwise try the node injector.
const value = lookupTokenUsingNodeInjector(tNode, lView, token, flags, NOT_FOUND);
if (value !== NOT_FOUND) {
return value;
}
}
// Finally, fall back to the module injector.
return lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue);
}
/**
* Returns the value associated to the given token from the node injector.
*
* @param tNode The Node where the search for the injector should start
* @param lView The `LView` that contains the `tNode`
* @param token The token to look for
* @param flags Injection flags
* @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
*/
function lookupTokenUsingNodeInjector(tNode, lView, token, flags, notFoundValue) {
const bloomHash = bloomHashBitOrFactory(token);
// If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
// so just call the factory function to create it.
if (typeof bloomHash === 'function') {
if (!enterDI(lView, tNode, flags)) {
// Failed to enter DI, try module injector instead. If a token is injected with the @Host
// flag, the module injector is not searched for that token in Ivy.
return flags & InjectFlags.Host
? notFoundValueOrThrow(notFoundValue, token, flags)
: lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue);
}
try {
let value;
if (ngDevMode) {
runInInjectorProfilerContext(new NodeInjector(getCurrentTNode(), getLView()), token, () => {
value = bloomHash(flags);
if (value != null) {
emitInstanceCreatedByInjectorEvent(value);
}
});
}
else {
value = bloomHash(flags);
}
if (value == null && !(flags & InjectFlags.Optional)) {
throwProviderNotFoundError(token);
}
else {
return value;
}
}
finally {
leaveDI();
}
}
else if (typeof bloomHash === 'number') {
// A reference to the previous injector TView that was found while climbing the element
// injector tree. This is used to know if viewProviders can be accessed on the current
// injector.
let previousTView = null;
let injectorIndex = getInjectorIndex(tNode, lView);
let parentLocation = NO_PARENT_INJECTOR;
let hostTElementNode = flags & InjectFlags.Host ? lView[DECLARATION_COMPONENT_VIEW][T_HOST] : null;
// If we should skip this injector, or if there is no injector on this node, start by
// searching the parent injector.
if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
parentLocation =
injectorIndex === -1
? getParentInjectorLocation(tNode, lView)
: lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */];
if (parentLocation === NO_PARENT_INJECTOR || !shouldSearchParent(flags, false)) {
injectorIndex = -1;
}
else {
previousTView = lView[TVIEW];
injectorIndex = getParentInjectorIndex(parentLocation);
lView = getParentInjectorView(parentLocation, lView);
}
}
// Traverse up the injector tree until we find a potential match or until we know there
// *isn't* a match.
while (injectorIndex !== -1) {
ngDevMode && assertNodeInjector(lView, injectorIndex);
// Check the current injector. If it matches, see if it contains token.
const tView = lView[TVIEW];
ngDevMode &&
assertTNodeForLView(tView.data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */], lView);
if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
// At this point, we have an injector which *may* contain the token, so we step through
// the providers and directives associated with the injector's corresponding node to get
// the instance.
const instance = searchTokensOnInjector(injectorIndex, lView, token, previousTView, flags, hostTElementNode);
if (instance !== NOT_FOUND) {
return instance;
}
}
parentLocation = lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */];
if (parentLocation !== NO_PARENT_INJECTOR &&
shouldSearchParent(flags, lView[TVIEW].data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */] === hostTElementNode) &&
bloomHasToken(bloomHash, injectorIndex, lView)) {
// The def wasn't found anywhere on this node, so it was a false positive.
// Traverse up the tree and continue searching.
previousTView = tView;
injectorIndex = getParentInjectorIndex(parentLocation);
lView = getParentInjectorView(parentLocation, lView);
}
else {
// If we should not search parent OR If the ancestor bloom filter value does not have the
// bit corresponding to the directive we can give up on traversing up to find the specific
// injector.
injectorIndex = -1;
}
}
}
return notFoundValue;
}
function searchTokensOnInjector(injectorIndex, lView, token, previousTView, flags, hostTElementNode) {
const currentTView = lView[TVIEW];
const tNode = currentTView.data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */];
// First, we need to determine if view providers can be accessed by the starting element.
// There are two possibilities
const canAccessViewProviders = previousTView == null
? // 1) This is the first invocation `previousTView == null` which means that we are at the
// `TNode` of where injector is starting to look. In such a case the only time we are allowed
// to look into the ViewProviders is if:
// - we are on a component
// - AND the injector set `includeViewProviders` to true (implying that the token can see
// ViewProviders because it is the Component or a Service which itself was declared in
// ViewProviders)
isComponentHost(tNode) && includeViewProviders
: // 2) `previousTView != null` which means that we are now walking across the parent nodes.
// In such a case we are only allowed to look into the ViewProviders if:
// - We just crossed from child View to Parent View `previousTView != currentTView`
// - AND the parent TNode is an Element.
// This means that we just came from the Component's View and therefore are allowed to see
// into the ViewProviders.
previousTView != currentTView && (tNode.type & 3 /* TNodeType.AnyRNode */) !== 0;
// This special case happens when there is a @host on the inject and when we are searching
// on the host element node.
const isHostSpecialCase = flags & InjectFlags.Host && hostTElementNode === tNode;
const injectableIdx = locateDirectiveOrProvider(tNode, currentTView, token, canAccessViewProviders, isHostSpecialCase);
if (injectableIdx !== null) {
return getNodeInjectable(lView, currentTView, injectableIdx, tNode);
}
else {
return NOT_FOUND;
}
}
/**
* Searches for the given token among the node's directives and providers.
*
* @param tNode TNode on which directives are present.
* @param tView The tView we are currently processing
* @param token Provider token or type of a directive to look for.
* @param canAccessViewProviders Whether view providers should be considered.
* @param isHostSpecialCase Whether the host special case applies.
* @returns Index of a found directive or provider, or null when none found.
*/
function locateDirectiveOrProvider(tNode, tView, token, canAccessViewProviders, isHostSpecialCase) {
const nodeProviderIndexes = tNode.providerIndexes;
const tInjectables = tView.data;
const injectablesStart = nodeProviderIndexes & 1048575 /* TNodeProviderIndexes.ProvidersStartIndexMask */;
const directivesStart = tNode.directiveStart;
const directiveEnd = tNode.directiveEnd;
const cptViewProvidersCount = nodeProviderIndexes >> 20 /* TNodeProviderIndexes.CptViewProvidersCountShift */;
const startingIndex = canAccessViewProviders
? injectablesStart
: injectablesStart + cptViewProvidersCount;
// When the host special case applies, only the viewProviders and the component are visible
const endIndex = isHostSpecialCase ? injectablesStart + cptViewProvidersCount : directiveEnd;
for (let i = startingIndex; i < endIndex; i++) {
const providerTokenOrDef = tInjectables[i];
if ((i < directivesStart && token === providerTokenOrDef) ||
(i >= directivesStart && providerTokenOrDef.type === token)) {
return i;
}
}
if (isHostSpecialCase) {
const dirDef = tInjectables[directivesStart];
if (dirDef && isComponentDef(dirDef) && dirDef.type === token) {
return directivesStart;
}
}
return null;
}
/**
* Retrieve or instantiate the injectable from the `LView` at particular `index`.
*
* This function checks to see if the value has already been instantiated and if so returns the
* cached `injectable`. Otherwise if it detects that the value is still a factory it
* instantiates the `injectable` and caches the value.
*/
function getNodeInjectable(lView, tView, index, tNode) {
let value = lView[index];
const tData = tView.data;
if (isFactory(value)) {
const factory = value;
if (factory.resolving) {
throwCyclicDependencyError(stringifyForError(tData[index]));
}
const previousIncludeViewProviders = setIncludeViewProviders(factory.canSeeViewProviders);
factory.resolving = true;
let prevInjectContext;
if (ngDevMode) {
// tData indexes mirror the concrete instances in its corresponding LView.
// lView[index] here is either the injectable instace itself or a factory,
// therefore tData[index] is the constructor of that injectable or a
// definition object that contains the constructor in a `.type` field.
const token = tData[index].type || tData[index];
const injector = new NodeInjector(tNode, lView);
prevInjectContext = setInjectorProfilerContext({ injector, token });
}
const previousInjectImplementation = factory.injectImpl
? setInjectImplementation(factory.injectImpl)
: null;
const success = enterDI(lView, tNode, InjectFlags.Default);
ngDevMode &&
assertEqual(success, true, "Because flags do not contain `SkipSelf' we expect this to always succeed.");
try {
value = lView[index] = factory.factory(undefined, tData, lView, tNode);
ngDevMode && emitInstanceCreatedByInjectorEvent(value);
// This code path is hit for both directives and providers.
// For perf reasons, we want to avoid searching for hooks on providers.
// It does no harm to try (the hooks just won't exist), but the extra
// checks are unnecessary and this is a hot path. So we check to see
// if the index of the dependency is in the directive range for this
// tNode. If it's not, we know it's a provider and skip hook registration.
if (tView.firstCreatePass && index >= tNode.directiveStart) {
ngDevMode && assertDirectiveDef(tData[index]);
registerPreOrderHooks(index, tData[index], tView);
}
}
finally {
ngDevMode && setInjectorProfilerContext(prevInjectContext);
previousInjectImplementation !== null &&
setInjectImplementation(previousInjectImplementation);
setIncludeViewProviders(previousIncludeViewProviders);
factory.resolving = false;
leaveDI();
}
}
return value;
}
/**
* Returns the bit in an injector's bloom filter that should be used to determine whether or not
* the directive might be provided by the injector.
*
* When a directive is public, it is added to the bloom filter and given a unique ID that can be
* retrieved on the Type. When the directive isn't public or the token is not a directive `null`
* is returned as the node injector can not possibly provide that token.
*
* @param token the injection token
* @returns the matching bit to check in the bloom filter or `null` if the token is not known.
* When the returned value is negative then it represents special values such as `Injector`.
*/
function bloomHashBitOrFactory(token) {
ngDevMode && assertDefined(token, 'token must be defined');
if (typeof token === 'string') {
return token.charCodeAt(0) || 0;
}
const tokenId =
// First check with `hasOwnProperty` so we don't get an inherited ID.
token.hasOwnProperty(NG_ELEMENT_ID) ? token[NG_ELEMENT_ID] : undefined;
// Negative token IDs are used for special objects such as `Injector`
if (typeof tokenId === 'number') {
if (tokenId >= 0) {
return tokenId & BLOOM_MASK;
}
else {
ngDevMode &&
assertEqual(tokenId, -1 /* InjectorMarkers.Injector */, 'Expecting to get Special Injector Id');
return createNodeInjector;
}
}
else {
return tokenId;
}
}
function bloomHasToken(bloomHash, injectorIndex, injectorView) {
// Create a mask that targets the specific bit associated with the directive we're looking for.
// JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
// to bit positions 0 - 31 in a 32 bit integer.
const mask = 1 << bloomHash;
// Each bloom bucket in `injectorView` represents `BLOOM_BUCKET_BITS` number of bits of
// `bloomHash`. Any bits in `bloomHash` beyond `BLOOM_BUCKET_BITS` indicate the bucket offset
// that should be used.
const value = injectorView[injectorIndex + (bloomHash >> BLOOM_BUCKET_BITS)];
// If the bloom filter value has the bit corresponding to the directive's bloomBit flipped on,
// this injector is a potential match.
return !!(value & mask);
}
/** Returns true if flags prevent parent injector from being searched for tokens */
function shouldSearchParent(flags, isFirstHostTNode) {
return !(flags & InjectFlags.Self) && !(flags & InjectFlags.Host && isFirstHostTNode);
}
function getNodeInjectorLView(nodeInjector) {
return nodeInjector._lView;
}
function getNodeInjectorTNode(nodeInjector) {
return nodeInjector._tNode;
}
class NodeInjector {
constructor(_tNode, _lView) {
this._tNode = _tNode;
this._lView = _lView;
}
get(token, notFoundValue, flags) {
return getOrCreateInjectable(this._tNode, this._lView, token, convertToBitFlags(flags), notFoundValue);
}
}
/** Creates a `NodeInjector` for the current node. */
function createNodeInjector() {
return new NodeInjector(getCurrentTNode(), getLView());
}
/**
* @codeGenApi
*/
function ɵɵgetInheritedFactory(type) {
return noSideEffects(() => {
const ownConstructor = type.prototype.constructor;
const ownFactory = ownConstructor[NG_FACTORY_DEF] || getFactoryOf(ownConstructor);
const objectPrototype = Object.prototype;
let parent = Object.getPrototypeOf(type.prototype).constructor;
// Go up the prototype until we hit `Object`.
while (parent && parent !== objectPrototype) {
const factory = parent[NG_FACTORY_DEF] || getFactoryOf(parent);
// If we hit something that has a factory and the factory isn't the same as the type,
// we've found the inherited factory. Note the check that the factory isn't the type's
// own factory is redundant in most cases, but if the user has custom decorators on the
// class, this lookup will start one level down in the prototype chain, causing us to
// find the own factory first and potentially triggering an infinite loop downstream.
if (factory && factory !== ownFactory) {
return factory;
}
parent = Object.getPrototypeOf(parent);
}
// There is no factory defined. Either this was improper usage of inheritance
// (no Angular decorator on the superclass) or there is no constructor at all
// in the inheritance chain. Since the two cases cannot be distinguished, the
// latter has to be assumed.
return (t) => new t();
});
}
function getFactoryOf(type) {
if (isForwardRef(type)) {
return () => {
const factory = getFactoryOf(resolveForwardRef(type));
return factory && factory();
};
}
return getFactoryDef(type);
}
/**
* Returns a value from the closest embedded or node injector.
*
* @param tNode The Node where the search for the injector should start
* @param lView The `LView` that contains the `tNode`
* @param token The token to look for
* @param flags Injection flags
* @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
*/
function lookupTokenUsingEmbeddedInjector(tNode, lView, token, flags, notFoundValue) {
let currentTNode = tNode;
let currentLView = lView;
// When an LView with an embedded view injector is inserted, it'll likely be interlaced with
// nodes who may have injectors (e.g. node injector -> embedded view injector -> node injector).
// Since the bloom filters for the node injectors have already been constructed and we don't
// have a way of extracting the records from an injector, the only way to maintain the correct
// hierarchy when resolving the value is to walk it node-by-node while attempting to resolve
// the token at each level.
while (currentTNode !== null &&
currentLView !== null &&
currentLView[FLAGS] & 2048 /* LViewFlags.HasEmbeddedViewInjector */ &&
!(currentLView[FLAGS] & 512 /* LViewFlags.IsRoot */)) {
ngDevMode && assertTNodeForLView(currentTNode, currentLView);
// Note that this lookup on the node injector is using the `Self` flag, because
// we don't want the node injector to look at any parent injectors since we
// may hit the embedded view injector first.
const nodeInjectorValue = lookupTokenUsingNodeInjector(currentTNode, currentLView, token, flags | InjectFlags.Self, NOT_FOUND);
if (nodeInjectorValue !== NOT_FOUND) {
return nodeInjectorValue;
}
// Has an explicit type due to a TS bug: https://github.com/microsoft/TypeScript/issues/33191
let parentTNode = currentTNode.parent;
// `TNode.parent` includes the parent within the current view only. If it doesn't exist,
// it means that we've hit the view boundary and we need to go up to the next view.
if (!parentTNode) {
// Before we go to the next LView, check if the token exists on the current embedded injector.
const embeddedViewInjector = currentLView[EMBEDDED_VIEW_INJECTOR];
if (embeddedViewInjector) {
const embeddedViewInjectorValue = embeddedViewInjector.get(token, NOT_FOUND, flags);
if (embeddedViewInjectorValue !== NOT_FOUND) {
return embeddedViewInjectorValue;
}
}
// Otherwise keep going up the tree.
parentTNode = getTNodeFromLView(currentLView);
currentLView = currentLView[DECLARATION_VIEW];
}
currentTNode = parentTNode;
}
return notFoundValue;
}
/** Gets the TNode associated with an LView inside of the declaration view. */
function getTNodeFromLView(lView) {
const tView = lView[TVIEW];
const tViewType = tView.type;
// The parent pointer differs based on `TView.type`.
if (tViewType === 2 /* TViewType.Embedded */) {
ngDevMode && assertDefined(tView.declTNode, 'Embedded TNodes should have declaration parents.');
return tView.declTNode;
}
else if (tViewType === 1 /* TViewType.Component */) {
// Components don't have `TView.declTNode` because each instance of component could be
// inserted in different location, hence `TView.declTNode` is meaningless.
return lView[T_HOST];
}
return null;
}
/**
* Facade for the attribute injection from DI.
*
* @codeGenApi
*/
function ɵɵinjectAttribute(attrNameToInject) {
return injectAttributeImpl(getCurrentTNode(), attrNameToInject);
}
/**
* Attribute decorator and metadata.
*
* @Annotation
* @publicApi
*/
const Attribute = makeParamDecorator('Attribute', (attributeName) => ({
attributeName,
__NG_ELEMENT_ID__: () => ɵɵinjectAttribute(attributeName),
}));
let _reflect = null;
function getReflect() {
return (_reflect = _reflect || new ReflectionCapabilities());
}
function reflectDependencies(type) {
return convertDependencies(getReflect().parameters(type));
}
function convertDependencies(deps) {
return deps.map((dep) => reflectDependency(dep));
}
function reflectDependency(dep) {
const meta = {
token: null,
attribute: null,
host: false,
optional: false,
self: false,
skipSelf: false,
};
if (Array.isArray(dep) && dep.length > 0) {
for (let j = 0; j < dep.length; j++) {
const param = dep[j];
if (param === undefined) {
// param may be undefined if type of dep is not set by ngtsc
continue;
}
const proto = Object.getPrototypeOf(param);
if (param instanceof Optional || proto.ngMetadataName === 'Optional') {
meta.optional = true;
}
else if (param instanceof SkipSelf || proto.ngMetadataName === 'SkipSelf') {
meta.skipSelf = true;
}
else if (param instanceof Self || proto.ngMetadataName === 'Self') {
meta.self = true;
}
else if (param instanceof Host || proto.ngMetadataName === 'Host') {
meta.host = true;
}
else if (param instanceof Inject) {
meta.token = param.token;
}
else if (param instanceof Attribute) {
if (param.attributeName === undefined) {
throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode && `Attribute name must be defined.`);
}
meta.attribute = param.attributeName;
}
else {
meta.token = param;
}
}
}
else if (dep === undefined || (Array.isArray(dep) && dep.length === 0)) {
meta.token = null;
}
else {
meta.token = dep;
}
return meta;
}
/**
* Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting
* injectable def (`ɵprov`) onto the injectable type.
*/
function compileInjectable(type, meta) {
let ngInjectableDef = null;
let ngFactoryDef = null;
// if NG_PROV_DEF is already defined on this class then don't overwrite it
if (!type.hasOwnProperty(NG_PROV_DEF)) {
Object.defineProperty(type, NG_PROV_DEF, {
get: () => {
if (ngInjectableDef === null) {
const compiler = getCompilerFacade({
usage: 0 /* JitCompilerUsage.Decorator */,
kind: 'injectable',
type,
});
ngInjectableDef = compiler.compileInjectable(angularCoreDiEnv, `ng:///${type.name}/ɵprov.js`, getInjectableMetadata(type, meta));
}
return ngInjectableDef;
},
});
}
// if NG_FACTORY_DEF is already defined on this class then don't overwrite it
if (!type.hasOwnProperty(NG_FACTORY_DEF)) {
Object.defineProperty(type, NG_FACTORY_DEF, {
get: () => {
if (ngFactoryDef === null) {
const compiler = getCompilerFacade({
usage: 0 /* JitCompilerUsage.Decorator */,
kind: 'injectable',
type,
});
ngFactoryDef = compiler.compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, {
name: type.name,
type,
typeArgumentCount: 0, // In JIT mode types are not available nor used.
deps: reflectDependencies(type),
target: compiler.FactoryTarget.Injectable,
});
}
return ngFactoryDef;
},
// Leave this configurable so that the factories from directives or pipes can take precedence.
configurable: true,
});
}
}
const USE_VALUE = getClosureSafeProperty({
provide: String,
useValue: getClosureSafeProperty,
});
function isUseClassProvider(meta) {
return meta.useClass !== undefined;
}
function isUseValueProvider(meta) {
return USE_VALUE in meta;
}
function isUseFactoryProvider(meta) {
return meta.useFactory !== undefined;
}
function isUseExistingProvider(meta) {
return meta.useExisting !== undefined;
}
function getInjectableMetadata(type, srcMeta) {
// Allow the compilation of a class with a `@Injectable()` decorator without parameters
const meta = srcMeta || { providedIn: null };
const compilerMeta = {
name: type.name,
type: type,
typeArgumentCount: 0,
providedIn: meta.providedIn,
};
if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== undefined) {
compilerMeta.deps = convertDependencies(meta.deps);
}
// Check to see if the user explicitly provided a `useXxxx` property.
if (isUseClassProvider(meta)) {
compilerMeta.useClass = meta.useClass;
}
else if (isUseValueProvider(meta)) {
compilerMeta.useValue = meta.useValue;
}
else if (isUseFactoryProvider(meta)) {
compilerMeta.useFactory = meta.useFactory;
}
else if (isUseExistingProvider(meta)) {
compilerMeta.useExisting = meta.useExisting;
}
return compilerMeta;
}
/**
* Injectable decorator and metadata.
*
* @Annotation
* @publicApi
*/
const Injectable = makeDecorator('Injectable', undefined, undefined, undefined, (type, meta) => compileInjectable(type, meta));
/**
* 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 { this.THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND; }
static { this.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 { this.ɵprov = ɵɵdefineInjectable({
token: Injector,
providedIn: 'any',
factory: () => ɵɵinject(INJECTOR$1),
}); }
/**
* @internal
* @nocollapse
*/
static { this.__NG_ELEMENT_ID__ = -1 /* InjectorMarkers.Injector */; }
}
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/**
* Creates a token that can be used to inject static attributes of the host node.
*
* @usageNotes
* ### Injecting an attribute that is known to exist
* ```typescript
* @Directive()
* class MyDir {
* attr: string = inject(new HostAttributeToken('some-attr'));
* }
* ```
*
* ### Optionally injecting an attribute
* ```typescript
* @Directive()
* class MyDir {
* attr: string | null = inject(new HostAttributeToken('some-attr'), {optional: true});
* }
* ```
* @publicApi
*/
class HostAttributeToken {
constructor(attributeName) {
this.attributeName = attributeName;
/** @internal */
this.__NG_ELEMENT_ID__ = () => ɵɵinjectAttribute(this.attributeName);
}
toString() {
return `HostAttributeToken ${this.attributeName}`;
}
}
/**
* A token that can be used to inject the tag name of the host node.
*
* @usageNotes
* ### Injecting a tag name that is known to exist
* ```typescript
* @Directive()
* class MyDir {
* tagName: string = inject(HOST_TAG_NAME);
* }
* ```
*
* ### Optionally injecting a tag name
* ```typescript
* @Directive()
* class MyDir {
* tagName: string | null = inject(HOST_TAG_NAME, {optional: true});
* }
* ```
* @publicApi
*/
const HOST_TAG_NAME = new InjectionToken(ngDevMode ? 'HOST_TAG_NAME' : '');
// HOST_TAG_NAME should be resolved at the current node, similar to e.g. ElementRef,
// so we manually specify __NG_ELEMENT_ID__ here, instead of using a factory.
// tslint:disable-next-line:no-toplevel-property-access
HOST_TAG_NAME.__NG_ELEMENT_ID__ = (flags) => {
const tNode = getCurrentTNode();
if (tNode === null) {
throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode &&
'HOST_TAG_NAME can only be injected in directives and components ' +
'during construction time (in a class constructor or as a class field initializer)');
}
if (tNode.type & 2 /* TNodeType.Element */) {
return tNode.value;
}
if (flags & InjectFlags.Optional) {
return null;
}
throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode &&
`HOST_TAG_NAME was used on ${getDevModeNodeName(tNode)} which doesn't have an underlying element in the DOM. ` +
`This is invalid, and so the dependency should be marked as optional.`);
};
function getDevModeNodeName(tNode) {
if (tNode.type & 8 /* TNodeType.ElementContainer */) {
return 'an ';
}
else if (tNode.type & 4 /* TNodeType.Container */) {
return 'an ';
}
else if (tNode.type & 128 /* TNodeType.LetDeclaration */) {
return 'an @let declaration';
}
else {
return 'a node';
}
}
/**
* @module
* @description
* The `di` module provides dependency injection container services.
*/
/**
* This file should not be necessary because node resolution should just default to `./di/index`!
*
* However it does not seem to work and it breaks:
* - //packages/animations/browser/test:test_web_chromium-local
* - //packages/compiler-cli/test:extract_i18n
* - //packages/compiler-cli/test:ngc
* - //packages/compiler-cli/test:perform_watch
* - //packages/compiler-cli/test/diagnostics:check_types
* - //packages/compiler-cli/test/transformers:test
* - //packages/compiler/test:test
* - //tools/public_api_guard:core_api
*
* Remove this file once the above is solved or wait until `ngc` is deleted and then it should be
* safe to delete this file.
*/
const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
function wrappedError(message, originalError) {
const msg = `${message} caused by: ${originalError instanceof Error ? originalError.message : originalError}`;
const error = Error(msg);
error[ERROR_ORIGINAL_ERROR] = originalError;
return error;
}
function getOriginalError(error) {
return error[ERROR_ORIGINAL_ERROR];
}
const SCHEDULE_IN_ROOT_ZONE_DEFAULT = true;
/**
* `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 { this.__NG_ELEMENT_ID__ = injectDestroyRef; }
/**
* @internal
* @nocollapse
*/
static { this.__NG_ENV_ID__ = (injector) => injector; }
}
class NodeInjectorDestroyRef extends DestroyRef {
constructor(_lView) {
super();
this._lView = _lView;
}
onDestroy(callback) {
storeLViewOnDestroy(this._lView, callback);
return () => removeLViewOnDestroy(this._lView, callback);
}
}
function injectDestroyRef() {
return new NodeInjectorDestroyRef(getLView());
}
/**
* Internal implementation of the pending tasks service.
*/
class PendingTasks {
constructor() {
this.taskId = 0;
this.pendingTasks = new Set();
this.hasPendingTasks = new BehaviorSubject(false);
}
get _hasPendingTasks() {
return this.hasPendingTasks.value;
}
add() {
if (!this._hasPendingTasks) {
this.hasPendingTasks.next(true);
}
const taskId = this.taskId++;
this.pendingTasks.add(taskId);
return taskId;
}
remove(taskId) {
this.pendingTasks.delete(taskId);
if (this.pendingTasks.size === 0 && this._hasPendingTasks) {
this.hasPendingTasks.next(false);
}
}
ngOnDestroy() {
this.pendingTasks.clear();
if (this._hasPendingTasks) {
this.hasPendingTasks.next(false);
}
}
/** @nocollapse */
static { this.ɵprov = ɵɵdefineInjectable({
token: PendingTasks,
providedIn: 'root',
factory: () => new PendingTasks(),
}); }
}
/**
* Experimental 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
* ```typescript
* const pendingTasks = inject(ExperimentalPendingTasks);
* const taskCleanup = pendingTasks.add();
* // do work that should block application's stability and then:
* taskCleanup();
* ```
*
* This API is experimental. Neither the shape, nor the underlying behavior is stable and can change
* in patch versions. We will iterate on the exact API based on the feedback and our understanding
* of the problem and solution space.
*
* @publicApi
* @experimental
*/
class ExperimentalPendingTasks {
constructor() {
this.internalPendingTasks = inject(PendingTasks);
}
/**
* 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 () => this.internalPendingTasks.remove(taskId);
}
/** @nocollapse */
static { this.ɵprov = ɵɵdefineInjectable({
token: ExperimentalPendingTasks,
providedIn: 'root',
factory: () => new ExperimentalPendingTasks(),
}); }
}
class EventEmitter_ extends Subject {
constructor(isAsync = false) {
super();
this.destroyRef = undefined;
this.pendingTasks = undefined;
this.__isAsync = isAsync;
// Attempt to retrieve a `DestroyRef` and `PendingTasks` optionally.
// For backwards compatibility reasons, this cannot be required.
if (isInInjectionContext()) {
this.destroyRef = inject(DestroyRef, { optional: true }) ?? undefined;
this.pendingTasks = inject(PendingTasks, { optional: true }) ?? undefined;
}
}
emit(value) {
const prevConsumer = setActiveConsumer$1(null);
try {
super.next(value);
}
finally {
setActiveConsumer$1(prevConsumer);
}
}
subscribe(observerOrNext, error, complete) {
let nextFn = observerOrNext;
let errorFn = error || (() => null);
let completeFn = complete;
if (observerOrNext && typeof observerOrNext === 'object') {
const observer = observerOrNext;
nextFn = observer.next?.bind(observer);
errorFn = observer.error?.bind(observer);
completeFn = observer.complete?.bind(observer);
}
if (this.__isAsync) {
errorFn = this.wrapInTimeout(errorFn);
if (nextFn) {
nextFn = this.wrapInTimeout(nextFn);
}
if (completeFn) {
completeFn = this.wrapInTimeout(completeFn);
}
}
const sink = super.subscribe({ next: nextFn, error: errorFn, complete: completeFn });
if (observerOrNext instanceof Subscription) {
observerOrNext.add(sink);
}
return sink;
}
wrapInTimeout(fn) {
return (value) => {
const taskId = this.pendingTasks?.add();
setTimeout(() => {
fn(value);
if (taskId !== undefined) {
this.pendingTasks?.remove(taskId);
}
});
};
}
}
/**
* @publicApi
*/
const EventEmitter = EventEmitter_;
function noop(...args) {
// Do nothing.
}
/**
* Gets a scheduling function that runs the callback after the first of setTimeout and
* requestAnimationFrame resolves.
*
* - `requestAnimationFrame` ensures that change detection runs ahead of a browser repaint.
* This ensures that the create and update passes of a change detection always happen
* in the same frame.
* - When the browser is resource-starved, `rAF` can execute _before_ a `setTimeout` because
* rendering is a very high priority process. This means that `setTimeout` cannot guarantee
* same-frame create and update pass, when `setTimeout` is used to schedule the update phase.
* - While `rAF` gives us the desirable same-frame updates, it has two limitations that
* prevent it from being used alone. First, it does not run in background tabs, which would
* prevent Angular from initializing an application when opened in a new tab (for example).
* Second, repeated calls to requestAnimationFrame will execute at the refresh rate of the
* hardware (~16ms for a 60Hz display). This would cause significant slowdown of tests that
* are written with several updates and asserts in the form of "update; await stable; assert;".
* - Both `setTimeout` and `rAF` are able to "coalesce" several events from a single user
* interaction into a single change detection. Importantly, this reduces view tree traversals when
* compared to an alternative timing mechanism like `queueMicrotask`, where change detection would
* then be interleaves between each event.
*
* By running change detection after the first of `setTimeout` and `rAF` to execute, we get the
* best of both worlds.
*
* @returns a function to cancel the scheduled callback
*/
function scheduleCallbackWithRafRace(callback) {
let timeoutId;
let animationFrameId;
function cleanup() {
callback = noop;
try {
if (animationFrameId !== undefined && typeof cancelAnimationFrame === 'function') {
cancelAnimationFrame(animationFrameId);
}
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
}
catch {
// Clearing/canceling can fail in tests due to the timing of functions being patched and unpatched
// Just ignore the errors - we protect ourselves from this issue by also making the callback a no-op.
}
}
timeoutId = setTimeout(() => {
callback();
cleanup();
});
if (typeof requestAnimationFrame === 'function') {
animationFrameId = requestAnimationFrame(() => {
callback();
cleanup();
});
}
return () => cleanup();
}
function scheduleCallbackWithMicrotask(callback) {
queueMicrotask(() => callback());
return () => {
callback = noop;
};
}
class AsyncStackTaggingZoneSpec {
constructor(namePrefix, consoleAsyncStackTaggingImpl = console) {
this.name = 'asyncStackTagging for ' + namePrefix;
this.createTask = consoleAsyncStackTaggingImpl?.createTask ?? (() => null);
}
onScheduleTask(delegate, _current, target, task) {
task.consoleTask = this.createTask(`Zone - ${task.source || task.type}`);
return delegate.scheduleTask(target, task);
}
onInvokeTask(delegate, _currentZone, targetZone, task, applyThis, applyArgs) {
let ret;
if (task.consoleTask) {
ret = task.consoleTask.run(() => delegate.invokeTask(targetZone, task, applyThis, applyArgs));
}
else {
ret = delegate.invokeTask(targetZone, task, applyThis, applyArgs);
}
return ret;
}
}
const isAngularZoneProperty = 'isAngularZone';
const angularZoneInstanceIdProperty = isAngularZoneProperty + '_ID';
let ngZoneInstanceId = 0;
/**
* An injectable service for executing work inside or outside of the Angular zone.
*
* The most common use of this service is to optimize performance when starting a work consisting of
* one or more asynchronous tasks that don't require UI updates or error handling to be handled by
* Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks
* can reenter the Angular zone via {@link #run}.
*
*
*
* @usageNotes
* ### Example
*
* ```
* import {Component, NgZone} from '@angular/core';
* import {NgIf} from '@angular/common';
*
* @Component({
* selector: 'ng-zone-demo',
* template: `
* Demo: NgZone
*
* Progress: {{progress}}%
* = 100">Done processing {{label}} of Angular zone!
*
*
*
* `,
* })
* export class NgZoneDemo {
* progress: number = 0;
* label: string;
*
* constructor(private _ngZone: NgZone) {}
*
* // Loop inside the Angular zone
* // so the UI DOES refresh after each setTimeout cycle
* processWithinAngularZone() {
* this.label = 'inside';
* this.progress = 0;
* this._increaseProgress(() => console.log('Inside Done!'));
* }
*
* // Loop outside of the Angular zone
* // so the UI DOES NOT refresh after each setTimeout cycle
* processOutsideOfAngularZone() {
* this.label = 'outside';
* this.progress = 0;
* this._ngZone.runOutsideAngular(() => {
* this._increaseProgress(() => {
* // reenter the Angular zone and display done
* this._ngZone.run(() => { console.log('Outside Done!'); });
* });
* });
* }
*
* _increaseProgress(doneCallback: () => void) {
* this.progress += 1;
* console.log(`Current progress: ${this.progress}%`);
*
* if (this.progress < 100) {
* window.setTimeout(() => this._increaseProgress(doneCallback), 10);
* } else {
* doneCallback();
* }
* }
* }
* ```
*
* @publicApi
*/
class NgZone {
constructor(options) {
this.hasPendingMacrotasks = false;
this.hasPendingMicrotasks = false;
/**
* Whether there are no outstanding microtasks or macrotasks.
*/
this.isStable = true;
/**
* Notifies when code enters Angular Zone. This gets fired first on VM Turn.
*/
this.onUnstable = new EventEmitter(false);
/**
* Notifies when there is no more microtasks enqueued in the current VM Turn.
* This is a hint for Angular to do change detection, which may enqueue more microtasks.
* For this reason this event can fire multiple times per VM Turn.
*/
this.onMicrotaskEmpty = new EventEmitter(false);
/**
* Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which
* implies we are about to relinquish VM turn.
* This event gets called just once.
*/
this.onStable = new EventEmitter(false);
/**
* Notifies that an error has been delivered.
*/
this.onError = new EventEmitter(false);
const { enableLongStackTrace = false, shouldCoalesceEventChangeDetection = false, shouldCoalesceRunChangeDetection = false, scheduleInRootZone = SCHEDULE_IN_ROOT_ZONE_DEFAULT, } = options;
if (typeof Zone == 'undefined') {
throw new RuntimeError(908 /* RuntimeErrorCode.MISSING_ZONEJS */, ngDevMode && `In this configuration Angular requires Zone.js`);
}
Zone.assertZonePatched();
const self = this;
self._nesting = 0;
self._outer = self._inner = Zone.current;
// AsyncStackTaggingZoneSpec provides `linked stack traces` to show
// where the async operation is scheduled. For more details, refer
// to this article, https://developer.chrome.com/blog/devtools-better-angular-debugging/
// And we only import this AsyncStackTaggingZoneSpec in development mode,
// in the production mode, the AsyncStackTaggingZoneSpec will be tree shaken away.
if (ngDevMode) {
self._inner = self._inner.fork(new AsyncStackTaggingZoneSpec('Angular'));
}
if (Zone['TaskTrackingZoneSpec']) {
self._inner = self._inner.fork(new Zone['TaskTrackingZoneSpec']());
}
if (enableLongStackTrace && Zone['longStackTraceZoneSpec']) {
self._inner = self._inner.fork(Zone['longStackTraceZoneSpec']);
}
// if shouldCoalesceRunChangeDetection is true, all tasks including event tasks will be
// coalesced, so shouldCoalesceEventChangeDetection option is not necessary and can be skipped.
self.shouldCoalesceEventChangeDetection =
!shouldCoalesceRunChangeDetection && shouldCoalesceEventChangeDetection;
self.shouldCoalesceRunChangeDetection = shouldCoalesceRunChangeDetection;
self.callbackScheduled = false;
self.scheduleInRootZone = scheduleInRootZone;
forkInnerZoneWithAngularBehavior(self);
}
/**
This method checks whether the method call happens within an Angular Zone instance.
*/
static isInAngularZone() {
// Zone needs to be checked, because this method might be called even when NoopNgZone is used.
return typeof Zone !== 'undefined' && Zone.current.get(isAngularZoneProperty) === true;
}
/**
Assures that the method is called within the Angular Zone, otherwise throws an error.
*/
static assertInAngularZone() {
if (!NgZone.isInAngularZone()) {
throw new RuntimeError(909 /* RuntimeErrorCode.UNEXPECTED_ZONE_STATE */, ngDevMode && 'Expected to be in Angular Zone, but it is not!');
}
}
/**
Assures that the method is called outside of the Angular Zone, otherwise throws an error.
*/
static assertNotInAngularZone() {
if (NgZone.isInAngularZone()) {
throw new RuntimeError(909 /* RuntimeErrorCode.UNEXPECTED_ZONE_STATE */, ngDevMode && 'Expected to not be in Angular Zone, but it is!');
}
}
/**
* Executes the `fn` function synchronously within the Angular zone and returns value returned by
* the function.
*
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
* outside of the Angular zone (typically started via {@link #runOutsideAngular}).
*
* Any future tasks or microtasks scheduled from within this function will continue executing from
* within the Angular zone.
*
* If a synchronous error happens it will be rethrown and not reported via `onError`.
*/
run(fn, applyThis, applyArgs) {
return this._inner.run(fn, applyThis, applyArgs);
}
/**
* Executes the `fn` function synchronously within the Angular zone as a task and returns value
* returned by the function.
*
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
* outside of the Angular zone (typically started via {@link #runOutsideAngular}).
*
* Any future tasks or microtasks scheduled from within this function will continue executing from
* within the Angular zone.
*
* If a synchronous error happens it will be rethrown and not reported via `onError`.
*/
runTask(fn, applyThis, applyArgs, name) {
const zone = this._inner;
const task = zone.scheduleEventTask('NgZoneEvent: ' + name, fn, EMPTY_PAYLOAD, noop, noop);
try {
return zone.runTask(task, applyThis, applyArgs);
}
finally {
zone.cancelTask(task);
}
}
/**
* Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not
* rethrown.
*/
runGuarded(fn, applyThis, applyArgs) {
return this._inner.runGuarded(fn, applyThis, applyArgs);
}
/**
* Executes the `fn` function synchronously in Angular's parent zone and returns value returned by
* the function.
*
* Running functions via {@link #runOutsideAngular} allows you to escape Angular's zone and do
* work that
* doesn't trigger Angular change-detection or is subject to Angular's error handling.
*
* Any future tasks or microtasks scheduled from within this function will continue executing from
* outside of the Angular zone.
*
* Use {@link #run} to reenter the Angular zone and do work that updates the application model.
*/
runOutsideAngular(fn) {
return this._outer.run(fn);
}
}
const EMPTY_PAYLOAD = {};
function checkStable(zone) {
// TODO: @JiaLiPassion, should check zone.isCheckStableRunning to prevent
// re-entry. The case is:
//
// @Component({...})
// export class AppComponent {
// constructor(private ngZone: NgZone) {
// this.ngZone.onStable.subscribe(() => {
// this.ngZone.run(() => console.log('stable'););
// });
// }
//
// The onStable subscriber run another function inside ngZone
// which causes `checkStable()` re-entry.
// But this fix causes some issues in g3, so this fix will be
// launched in another PR.
if (zone._nesting == 0 && !zone.hasPendingMicrotasks && !zone.isStable) {
try {
zone._nesting++;
zone.onMicrotaskEmpty.emit(null);
}
finally {
zone._nesting--;
if (!zone.hasPendingMicrotasks) {
try {
zone.runOutsideAngular(() => zone.onStable.emit(null));
}
finally {
zone.isStable = true;
}
}
}
}
}
function delayChangeDetectionForEvents(zone) {
/**
* We also need to check _nesting here
* Consider the following case with shouldCoalesceRunChangeDetection = true
*
* ngZone.run(() => {});
* ngZone.run(() => {});
*
* We want the two `ngZone.run()` only trigger one change detection
* when shouldCoalesceRunChangeDetection is true.
* And because in this case, change detection run in async way(requestAnimationFrame),
* so we also need to check the _nesting here to prevent multiple
* change detections.
*/
if (zone.isCheckStableRunning || zone.callbackScheduled) {
return;
}
zone.callbackScheduled = true;
function scheduleCheckStable() {
scheduleCallbackWithRafRace(() => {
zone.callbackScheduled = false;
updateMicroTaskStatus(zone);
zone.isCheckStableRunning = true;
checkStable(zone);
zone.isCheckStableRunning = false;
});
}
if (zone.scheduleInRootZone) {
Zone.root.run(() => {
scheduleCheckStable();
});
}
else {
zone._outer.run(() => {
scheduleCheckStable();
});
}
updateMicroTaskStatus(zone);
}
function forkInnerZoneWithAngularBehavior(zone) {
const delayChangeDetectionForEventsDelegate = () => {
delayChangeDetectionForEvents(zone);
};
const instanceId = ngZoneInstanceId++;
zone._inner = zone._inner.fork({
name: 'angular',
properties: {
[isAngularZoneProperty]: true,
[angularZoneInstanceIdProperty]: instanceId,
[angularZoneInstanceIdProperty + instanceId]: true,
},
onInvokeTask: (delegate, current, target, task, applyThis, applyArgs) => {
// Prevent triggering change detection when the flag is detected.
if (shouldBeIgnoredByZone(applyArgs)) {
return delegate.invokeTask(target, task, applyThis, applyArgs);
}
try {
onEnter(zone);
return delegate.invokeTask(target, task, applyThis, applyArgs);
}
finally {
if ((zone.shouldCoalesceEventChangeDetection && task.type === 'eventTask') ||
zone.shouldCoalesceRunChangeDetection) {
delayChangeDetectionForEventsDelegate();
}
onLeave(zone);
}
},
onInvoke: (delegate, current, target, callback, applyThis, applyArgs, source) => {
try {
onEnter(zone);
return delegate.invoke(target, callback, applyThis, applyArgs, source);
}
finally {
if (zone.shouldCoalesceRunChangeDetection &&
// Do not delay change detection when the task is the scheduler's tick.
// We need to synchronously trigger the stability logic so that the
// zone-based scheduler can prevent a duplicate ApplicationRef.tick
// by first checking if the scheduler tick is running. This does seem a bit roundabout,
// but we _do_ still want to trigger all the correct events when we exit the zone.run
// (`onMicrotaskEmpty` and `onStable` _should_ emit; developers can have code which
// relies on these events happening after change detection runs).
// Note: `zone.callbackScheduled` is already in delayChangeDetectionForEventsDelegate
// but is added here as well to prevent reads of applyArgs when not necessary
!zone.callbackScheduled &&
!isSchedulerTick(applyArgs)) {
delayChangeDetectionForEventsDelegate();
}
onLeave(zone);
}
},
onHasTask: (delegate, current, target, hasTaskState) => {
delegate.hasTask(target, hasTaskState);
if (current === target) {
// We are only interested in hasTask events which originate from our zone
// (A child hasTask event is not interesting to us)
if (hasTaskState.change == 'microTask') {
zone._hasPendingMicrotasks = hasTaskState.microTask;
updateMicroTaskStatus(zone);
checkStable(zone);
}
else if (hasTaskState.change == 'macroTask') {
zone.hasPendingMacrotasks = hasTaskState.macroTask;
}
}
},
onHandleError: (delegate, current, target, error) => {
delegate.handleError(target, error);
zone.runOutsideAngular(() => zone.onError.emit(error));
return false;
},
});
}
function updateMicroTaskStatus(zone) {
if (zone._hasPendingMicrotasks ||
((zone.shouldCoalesceEventChangeDetection || zone.shouldCoalesceRunChangeDetection) &&
zone.callbackScheduled === true)) {
zone.hasPendingMicrotasks = true;
}
else {
zone.hasPendingMicrotasks = false;
}
}
function onEnter(zone) {
zone._nesting++;
if (zone.isStable) {
zone.isStable = false;
zone.onUnstable.emit(null);
}
}
function onLeave(zone) {
zone._nesting--;
checkStable(zone);
}
/**
* Provides a noop implementation of `NgZone` which does nothing. This zone requires explicit calls
* to framework to perform rendering.
*/
class NoopNgZone {
constructor() {
this.hasPendingMicrotasks = false;
this.hasPendingMacrotasks = false;
this.isStable = true;
this.onUnstable = new EventEmitter();
this.onMicrotaskEmpty = new EventEmitter();
this.onStable = new EventEmitter();
this.onError = new EventEmitter();
}
run(fn, applyThis, applyArgs) {
return fn.apply(applyThis, applyArgs);
}
runGuarded(fn, applyThis, applyArgs) {
return fn.apply(applyThis, applyArgs);
}
runOutsideAngular(fn) {
return fn();
}
runTask(fn, applyThis, applyArgs, name) {
return fn.apply(applyThis, applyArgs);
}
}
function shouldBeIgnoredByZone(applyArgs) {
return hasApplyArgsData(applyArgs, '__ignore_ng_zone__');
}
function isSchedulerTick(applyArgs) {
return hasApplyArgsData(applyArgs, '__scheduler_tick__');
}
function hasApplyArgsData(applyArgs, key) {
if (!Array.isArray(applyArgs)) {
return false;
}
// We should only ever get 1 arg passed through to invokeTask.
// Short circuit here incase that behavior changes.
if (applyArgs.length !== 1) {
return false;
}
return applyArgs[0]?.data?.[key] === true;
}
function getNgZone(ngZoneToUse = 'zone.js', options) {
if (ngZoneToUse === 'noop') {
return new NoopNgZone();
}
if (ngZoneToUse === 'zone.js') {
return new NgZone(options);
}
return ngZoneToUse;
}
// Public API for Zone
/**
* 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
*
* ```
* class MyErrorHandler implements ErrorHandler {
* handleError(error) {
* // do something with the exception
* }
* }
*
* @NgModule({
* providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
* })
* class MyModule {}
* ```
*
* @publicApi
*/
class ErrorHandler {
constructor() {
/**
* @internal
*/
this._console = console;
}
handleError(error) {
const originalError = this._findOriginalError(error);
this._console.error('ERROR', error);
if (originalError) {
this._console.error('ORIGINAL ERROR', originalError);
}
}
/** @internal */
_findOriginalError(error) {
let e = error && getOriginalError(error);
while (e && getOriginalError(e)) {
e = getOriginalError(e);
}
return e || null;
}
}
/**
* `InjectionToken` used to configure how to call the `ErrorHandler`.
*
* `NgZone` is provided by default today so the default (and only) implementation for this
* is calling `ErrorHandler.handleError` outside of the Angular zone.
*/
const INTERNAL_APPLICATION_ERROR_HANDLER = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'internal error handler' : '', {
providedIn: 'root',
factory: () => {
const zone = inject(NgZone);
const userErrorHandler = inject(ErrorHandler);
return (e) => zone.runOutsideAngular(() => userErrorHandler.handleError(e));
},
});
/**
* An `OutputEmitterRef` is created by the `output()` function and can be
* used to emit values to consumers of your directive or component.
*
* Consumers of your directive/component can bind to the output and
* subscribe to changes via the bound event syntax. For example:
*
* ```html
*
* ```
*
* @developerPreview
*/
class OutputEmitterRef {
constructor() {
this.destroyed = false;
this.listeners = null;
this.errorHandler = inject(ErrorHandler, { optional: true });
/** @internal */
this.destroyRef = inject(DestroyRef);
// Clean-up all listeners and mark as destroyed upon destroy.
this.destroyRef.onDestroy(() => {
this.destroyed = true;
this.listeners = null;
});
}
subscribe(callback) {
if (this.destroyed) {
throw new RuntimeError(953 /* RuntimeErrorCode.OUTPUT_REF_DESTROYED */, ngDevMode &&
'Unexpected subscription to destroyed `OutputRef`. ' +
'The owning directive/component is destroyed.');
}
(this.listeners ??= []).push(callback);
return {
unsubscribe: () => {
const idx = this.listeners?.indexOf(callback);
if (idx !== undefined && idx !== -1) {
this.listeners?.splice(idx, 1);
}
},
};
}
/** Emits a new value to the output. */
emit(value) {
if (this.destroyed) {
throw new RuntimeError(953 /* RuntimeErrorCode.OUTPUT_REF_DESTROYED */, ngDevMode &&
'Unexpected emit for destroyed `OutputRef`. ' +
'The owning directive/component is destroyed.');
}
if (this.listeners === null) {
return;
}
const previousConsumer = setActiveConsumer$1(null);
try {
for (const listenerFn of this.listeners) {
try {
listenerFn(value);
}
catch (err) {
this.errorHandler?.handleError(err);
}
}
}
finally {
setActiveConsumer$1(previousConsumer);
}
}
}
/** Gets the owning `DestroyRef` for the given output. */
function getOutputDestroyRef(ref) {
return ref.destroyRef;
}
/**
* The `output` function allows declaration of Angular outputs in
* directives and components.
*
* You can use outputs to emit values to parent directives and component.
* Parents can subscribe to changes via:
*
* - template event bindings. For example, `(myOutput)="doSomething($event)"`
* - programmatic subscription by using `OutputRef#subscribe`.
*
* @usageNotes
*
* To use `output()`, import the function from `@angular/core`.
*
* ```ts
* import {output} from '@angular/core';
* ```
*
* Inside your component, introduce a new class member and initialize
* it with a call to `output`.
*
* ```ts
* @Directive({
* ...
* })
* export class MyDir {
* nameChange = output(); // OutputEmitterRef
* onClick = output(); // OutputEmitterRef
* }
* ```
*
* You can emit values to consumers of your directive, by using
* the `emit` method from `OutputEmitterRef`.
*
* ```ts
* updateName(newName: string): void {
* this.nameChange.emit(newName);
* }
* ```
*
* @developerPreview
* @initializerApiFunction {"showTypesInSignaturePreview": true}
*/
function output(opts) {
ngDevMode && assertInInjectionContext(output);
return new OutputEmitterRef();
}
function inputFunction(initialValue, opts) {
ngDevMode && assertInInjectionContext(input);
return createInputSignal(initialValue, opts);
}
function inputRequiredFunction(opts) {
ngDevMode && assertInInjectionContext(input);
return createInputSignal(REQUIRED_UNSET_VALUE, opts);
}
/**
* The `input` function allows declaration of Angular inputs in directives
* and components.
*
* There are two variants of inputs that can be declared:
*
* 1. **Optional inputs** with an initial value.
* 2. **Required inputs** that consumers need to set.
*
* By default, the `input` function will declare optional inputs that
* always have an initial value. Required inputs can be declared
* using the `input.required()` function.
*
* Inputs are signals. The values of an input are exposed as a `Signal`.
* The signal always holds the latest value of the input that is bound
* from the parent.
*
* @usageNotes
* To use signal-based inputs, import `input` from `@angular/core`.
*
* ```
* import {input} from '@angular/core`;
* ```
*
* Inside your component, introduce a new class member and initialize
* it with a call to `input` or `input.required`.
*
* ```ts
* @Component({
* ...
* })
* export class UserProfileComponent {
* firstName = input(); // Signal
* lastName = input.required(); // Signal
* age = input(0) // Signal
* }
* ```
*
* Inside your component template, you can display values of the inputs
* by calling the signal.
*
* ```html
* {{firstName()}}
* ```
*
* @developerPreview
* @initializerApiFunction
*/
const input = (() => {
// Note: This may be considered a side-effect, but nothing will depend on
// this assignment, unless this `input` constant export is accessed. It's a
// self-contained side effect that is local to the user facing`input` export.
inputFunction.required = inputRequiredFunction;
return inputFunction;
})();
/**
* Creates an ElementRef from the most recent node.
*
* @returns The ElementRef instance to use
*/
function injectElementRef() {
return createElementRef(getCurrentTNode(), getLView());
}
/**
* Creates an ElementRef given a node.
*
* @param tNode The node for which you'd like an ElementRef
* @param lView The view to which the node belongs
* @returns The ElementRef instance to use
*/
function createElementRef(tNode, lView) {
return new ElementRef(getNativeByTNode(tNode, lView));
}
/**
* A wrapper around a native element inside of a View.
*
* An `ElementRef` is backed by a render-specific element. In the browser, this is usually a DOM
* element.
*
* @security Permitting direct access to the DOM can make your application more vulnerable to
* XSS attacks. Carefully review any use of `ElementRef` in your code. For more detail, see the
* [Security Guide](https://g.co/ng/security).
*
* @publicApi
*/
// Note: We don't expose things like `Injector`, `ViewContainer`, ... here,
// i.e. users have to ask for what they need. With that, we can build better analysis tools
// and could do better codegen in the future.
class ElementRef {
constructor(nativeElement) {
this.nativeElement = nativeElement;
}
/**
* @internal
* @nocollapse
*/
static { this.__NG_ELEMENT_ID__ = injectElementRef; }
}
/**
* Unwraps `ElementRef` and return the `nativeElement`.
*
* @param value value to unwrap
* @returns `nativeElement` if `ElementRef` otherwise returns value as is.
*/
function unwrapElementRef(value) {
return value instanceof ElementRef ? value.nativeElement : value;
}
function symbolIterator() {
// @ts-expect-error accessing a private member
return this._results[Symbol.iterator]();
}
/**
* An unmodifiable list of items that Angular keeps up to date when the state
* of the application changes.
*
* The type of object that {@link ViewChildren}, {@link ContentChildren}, and {@link QueryList}
* provide.
*
* Implements an iterable interface, therefore it can be used in both ES6
* javascript `for (var i of items)` loops as well as in Angular templates with
* `*ngFor="let i of myList"`.
*
* Changes can be observed by subscribing to the changes `Observable`.
*
* NOTE: In the future this class will implement an `Observable` interface.
*
* @usageNotes
* ### Example
* ```typescript
* @Component({...})
* class Container {
* @ViewChildren(Item) items:QueryList- ;
* }
* ```
*
* @publicApi
*/
class QueryList {
static { Symbol.iterator; }
/**
* Returns `Observable` of `QueryList` notifying the subscriber of changes.
*/
get changes() {
return (this._changes ??= new EventEmitter());
}
/**
* @param emitDistinctChangesOnly Whether `QueryList.changes` should fire only when actual change
* has occurred. Or if it should fire when query is recomputed. (recomputing could resolve in
* the same result)
*/
constructor(_emitDistinctChangesOnly = false) {
this._emitDistinctChangesOnly = _emitDistinctChangesOnly;
this.dirty = true;
this._onDirty = undefined;
this._results = [];
this._changesDetected = false;
this._changes = undefined;
this.length = 0;
this.first = undefined;
this.last = undefined;
// This function should be declared on the prototype, but doing so there will cause the class
// declaration to have side-effects and become not tree-shakable. For this reason we do it in
// the constructor.
// [Symbol.iterator](): Iterator
{ ... }
const proto = QueryList.prototype;
if (!proto[Symbol.iterator])
proto[Symbol.iterator] = symbolIterator;
}
/**
* Returns the QueryList entry at `index`.
*/
get(index) {
return this._results[index];
}
/**
* See
* [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
*/
map(fn) {
return this._results.map(fn);
}
filter(fn) {
return this._results.filter(fn);
}
/**
* See
* [Array.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
*/
find(fn) {
return this._results.find(fn);
}
/**
* See
* [Array.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)
*/
reduce(fn, init) {
return this._results.reduce(fn, init);
}
/**
* See
* [Array.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
*/
forEach(fn) {
this._results.forEach(fn);
}
/**
* See
* [Array.some](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)
*/
some(fn) {
return this._results.some(fn);
}
/**
* Returns a copy of the internal results list as an Array.
*/
toArray() {
return this._results.slice();
}
toString() {
return this._results.toString();
}
/**
* Updates the stored data of the query list, and resets the `dirty` flag to `false`, so that
* on change detection, it will not notify of changes to the queries, unless a new change
* occurs.
*
* @param resultsTree The query results to store
* @param identityAccessor Optional function for extracting stable object identity from a value
* in the array. This function is executed for each element of the query result list while
* comparing current query list with the new one (provided as a first argument of the `reset`
* function) to detect if the lists are different. If the function is not provided, elements
* are compared as is (without any pre-processing).
*/
reset(resultsTree, identityAccessor) {
this.dirty = false;
const newResultFlat = flatten(resultsTree);
if ((this._changesDetected = !arrayEquals(this._results, newResultFlat, identityAccessor))) {
this._results = newResultFlat;
this.length = newResultFlat.length;
this.last = newResultFlat[this.length - 1];
this.first = newResultFlat[0];
}
}
/**
* Triggers a change event by emitting on the `changes` {@link EventEmitter}.
*/
notifyOnChanges() {
if (this._changes !== undefined && (this._changesDetected || !this._emitDistinctChangesOnly))
this._changes.emit(this);
}
/** @internal */
onDirty(cb) {
this._onDirty = cb;
}
/** internal */
setDirty() {
this.dirty = true;
this._onDirty?.();
}
/** internal */
destroy() {
if (this._changes !== undefined) {
this._changes.complete();
this._changes.unsubscribe();
}
}
}
/**
* The name of an attribute that can be added to the hydration boundary node
* (component host node) to disable hydration for the content within that boundary.
*/
const SKIP_HYDRATION_ATTR_NAME = 'ngSkipHydration';
/** Lowercase name of the `ngSkipHydration` attribute used for case-insensitive comparisons. */
const SKIP_HYDRATION_ATTR_NAME_LOWER_CASE = 'ngskiphydration';
/**
* Helper function to check if a given TNode has the 'ngSkipHydration' attribute.
*/
function hasSkipHydrationAttrOnTNode(tNode) {
const attrs = tNode.mergedAttrs;
if (attrs === null)
return false;
// only ever look at the attribute name and skip the values
for (let i = 0; i < attrs.length; i += 2) {
const value = attrs[i];
// This is a marker, which means that the static attributes section is over,
// so we can exit early.
if (typeof value === 'number')
return false;
if (typeof value === 'string' && value.toLowerCase() === SKIP_HYDRATION_ATTR_NAME_LOWER_CASE) {
return true;
}
}
return false;
}
/**
* Helper function to check if a given RElement has the 'ngSkipHydration' attribute.
*/
function hasSkipHydrationAttrOnRElement(rNode) {
return rNode.hasAttribute(SKIP_HYDRATION_ATTR_NAME);
}
/**
* Checks whether a TNode has a flag to indicate that it's a part of
* a skip hydration block.
*/
function hasInSkipHydrationBlockFlag(tNode) {
return (tNode.flags & 128 /* TNodeFlags.inSkipHydrationBlock */) === 128 /* TNodeFlags.inSkipHydrationBlock */;
}
/**
* Helper function that determines if a given node is within a skip hydration block
* by navigating up the TNode tree to see if any parent nodes have skip hydration
* attribute.
*/
function isInSkipHydrationBlock(tNode) {
if (hasInSkipHydrationBlockFlag(tNode)) {
return true;
}
let currentTNode = tNode.parent;
while (currentTNode) {
if (hasInSkipHydrationBlockFlag(tNode) || hasSkipHydrationAttrOnTNode(currentTNode)) {
return true;
}
currentTNode = currentTNode.parent;
}
return false;
}
/**
* Check if an i18n block is in a skip hydration section by looking at a parent TNode
* to determine if this TNode is in a skip hydration section or the TNode has
* the `ngSkipHydration` attribute.
*/
function isI18nInSkipHydrationBlock(parentTNode) {
return (hasInSkipHydrationBlockFlag(parentTNode) ||
hasSkipHydrationAttrOnTNode(parentTNode) ||
isInSkipHydrationBlock(parentTNode));
}
// Keeps track of the currently-active LViews.
const TRACKED_LVIEWS = new Map();
// Used for generating unique IDs for LViews.
let uniqueIdCounter = 0;
/** Gets a unique ID that can be assigned to an LView. */
function getUniqueLViewId() {
return uniqueIdCounter++;
}
/** Starts tracking an LView. */
function registerLView(lView) {
ngDevMode && assertNumber(lView[ID], 'LView must have an ID in order to be registered');
TRACKED_LVIEWS.set(lView[ID], lView);
}
/** Gets an LView by its unique ID. */
function getLViewById(id) {
ngDevMode && assertNumber(id, 'ID used for LView lookup must be a number');
return TRACKED_LVIEWS.get(id) || null;
}
/** Stops tracking an LView. */
function unregisterLView(lView) {
ngDevMode && assertNumber(lView[ID], 'Cannot stop tracking an LView that does not have an ID');
TRACKED_LVIEWS.delete(lView[ID]);
}
/**
* The internal view context which is specific to a given DOM element, directive or
* component instance. Each value in here (besides the LView and element node details)
* can be present, null or undefined. If undefined then it implies the value has not been
* looked up yet, otherwise, if null, then a lookup was executed and nothing was found.
*
* Each value will get filled when the respective value is examined within the getContext
* function. The component, element and each directive instance will share the same instance
* of the context.
*/
class LContext {
/** Component's parent view data. */
get lView() {
return getLViewById(this.lViewId);
}
constructor(
/**
* ID of the component's parent view data.
*/
lViewId,
/**
* The index instance of the node.
*/
nodeIndex,
/**
* The instance of the DOM node that is attached to the lNode.
*/
native) {
this.lViewId = lViewId;
this.nodeIndex = nodeIndex;
this.native = native;
}
}
/**
* Returns the matching `LContext` data for a given DOM node, directive or component instance.
*
* This function will examine the provided DOM element, component, or directive instance\'s
* monkey-patched property to derive the `LContext` data. Once called then the monkey-patched
* value will be that of the newly created `LContext`.
*
* If the monkey-patched value is the `LView` instance then the context value for that
* target will be created and the monkey-patch reference will be updated. Therefore when this
* function is called it may mutate the provided element\'s, component\'s or any of the associated
* directive\'s monkey-patch values.
*
* If the monkey-patch value is not detected then the code will walk up the DOM until an element
* is found which contains a monkey-patch reference. When that occurs then the provided element
* will be updated with a new context (which is then returned). If the monkey-patch value is not
* detected for a component/directive instance then it will throw an error (all components and
* directives should be automatically monkey-patched by ivy).
*
* @param target Component, Directive or DOM Node.
*/
function getLContext(target) {
let mpValue = readPatchedData(target);
if (mpValue) {
// only when it's an array is it considered an LView instance
// ... otherwise it's an already constructed LContext instance
if (isLView(mpValue)) {
const lView = mpValue;
let nodeIndex;
let component = undefined;
let directives = undefined;
if (isComponentInstance(target)) {
nodeIndex = findViaComponent(lView, target);
if (nodeIndex == -1) {
throw new Error('The provided component was not found in the application');
}
component = target;
}
else if (isDirectiveInstance(target)) {
nodeIndex = findViaDirective(lView, target);
if (nodeIndex == -1) {
throw new Error('The provided directive was not found in the application');
}
directives = getDirectivesAtNodeIndex(nodeIndex, lView);
}
else {
nodeIndex = findViaNativeElement(lView, target);
if (nodeIndex == -1) {
return null;
}
}
// the goal is not to fill the entire context full of data because the lookups
// are expensive. Instead, only the target data (the element, component, container, ICU
// expression or directive details) are filled into the context. If called multiple times
// with different target values then the missing target data will be filled in.
const native = unwrapRNode(lView[nodeIndex]);
const existingCtx = readPatchedData(native);
const context = existingCtx && !Array.isArray(existingCtx)
? existingCtx
: createLContext(lView, nodeIndex, native);
// only when the component has been discovered then update the monkey-patch
if (component && context.component === undefined) {
context.component = component;
attachPatchData(context.component, context);
}
// only when the directives have been discovered then update the monkey-patch
if (directives && context.directives === undefined) {
context.directives = directives;
for (let i = 0; i < directives.length; i++) {
attachPatchData(directives[i], context);
}
}
attachPatchData(context.native, context);
mpValue = context;
}
}
else {
const rElement = target;
ngDevMode && assertDomNode(rElement);
// if the context is not found then we need to traverse upwards up the DOM
// to find the nearest element that has already been monkey patched with data
let parent = rElement;
while ((parent = parent.parentNode)) {
const parentContext = readPatchedData(parent);
if (parentContext) {
const lView = Array.isArray(parentContext) ? parentContext : parentContext.lView;
// the edge of the app was also reached here through another means
// (maybe because the DOM was changed manually).
if (!lView) {
return null;
}
const index = findViaNativeElement(lView, rElement);
if (index >= 0) {
const native = unwrapRNode(lView[index]);
const context = createLContext(lView, index, native);
attachPatchData(native, context);
mpValue = context;
break;
}
}
}
}
return mpValue || null;
}
/**
* Creates an empty instance of a `LContext` context
*/
function createLContext(lView, nodeIndex, native) {
return new LContext(lView[ID], nodeIndex, native);
}
/**
* Takes a component instance and returns the view for that component.
*
* @param componentInstance
* @returns The component's view
*/
function getComponentViewByInstance(componentInstance) {
let patchedData = readPatchedData(componentInstance);
let lView;
if (isLView(patchedData)) {
const contextLView = patchedData;
const nodeIndex = findViaComponent(contextLView, componentInstance);
lView = getComponentLViewByIndex(nodeIndex, contextLView);
const context = createLContext(contextLView, nodeIndex, lView[HOST]);
context.component = componentInstance;
attachPatchData(componentInstance, context);
attachPatchData(context.native, context);
}
else {
const context = patchedData;
const contextLView = context.lView;
ngDevMode && assertLView(contextLView);
lView = getComponentLViewByIndex(context.nodeIndex, contextLView);
}
return lView;
}
/**
* This property will be monkey-patched on elements, components and directives.
*/
const MONKEY_PATCH_KEY_NAME = '__ngContext__';
function attachLViewId(target, data) {
target[MONKEY_PATCH_KEY_NAME] = data[ID];
}
/**
* Returns the monkey-patch value data present on the target (which could be
* a component, directive or a DOM node).
*/
function readLView(target) {
const data = readPatchedData(target);
if (isLView(data)) {
return data;
}
return data ? data.lView : null;
}
/**
* Assigns the given data to the given target (which could be a component,
* directive or DOM node instance) using monkey-patching.
*/
function attachPatchData(target, data) {
ngDevMode && assertDefined(target, 'Target expected');
// Only attach the ID of the view in order to avoid memory leaks (see #41047). We only do this
// for `LView`, because we have control over when an `LView` is created and destroyed, whereas
// we can't know when to remove an `LContext`.
if (isLView(data)) {
target[MONKEY_PATCH_KEY_NAME] = data[ID];
registerLView(data);
}
else {
target[MONKEY_PATCH_KEY_NAME] = data;
}
}
/**
* Returns the monkey-patch value data present on the target (which could be
* a component, directive or a DOM node).
*/
function readPatchedData(target) {
ngDevMode && assertDefined(target, 'Target expected');
const data = target[MONKEY_PATCH_KEY_NAME];
return typeof data === 'number' ? getLViewById(data) : data || null;
}
function readPatchedLView(target) {
const value = readPatchedData(target);
if (value) {
return (isLView(value) ? value : value.lView);
}
return null;
}
function isComponentInstance(instance) {
return instance && instance.constructor && instance.constructor.ɵcmp;
}
function isDirectiveInstance(instance) {
return instance && instance.constructor && instance.constructor.ɵdir;
}
/**
* Locates the element within the given LView and returns the matching index
*/
function findViaNativeElement(lView, target) {
const tView = lView[TVIEW];
for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
if (unwrapRNode(lView[i]) === target) {
return i;
}
}
return -1;
}
/**
* Locates the next tNode (child, sibling or parent).
*/
function traverseNextElement(tNode) {
if (tNode.child) {
return tNode.child;
}
else if (tNode.next) {
return tNode.next;
}
else {
// Let's take the following template: text
// After checking the text node, we need to find the next parent that has a "next" TNode,
// in this case the parent `div`, so that we can find the component.
while (tNode.parent && !tNode.parent.next) {
tNode = tNode.parent;
}
return tNode.parent && tNode.parent.next;
}
}
/**
* Locates the component within the given LView and returns the matching index
*/
function findViaComponent(lView, componentInstance) {
const componentIndices = lView[TVIEW].components;
if (componentIndices) {
for (let i = 0; i < componentIndices.length; i++) {
const elementComponentIndex = componentIndices[i];
const componentView = getComponentLViewByIndex(elementComponentIndex, lView);
if (componentView[CONTEXT] === componentInstance) {
return elementComponentIndex;
}
}
}
else {
const rootComponentView = getComponentLViewByIndex(HEADER_OFFSET, lView);
const rootComponent = rootComponentView[CONTEXT];
if (rootComponent === componentInstance) {
// we are dealing with the root element here therefore we know that the
// element is the very first element after the HEADER data in the lView
return HEADER_OFFSET;
}
}
return -1;
}
/**
* Locates the directive within the given LView and returns the matching index
*/
function findViaDirective(lView, directiveInstance) {
// if a directive is monkey patched then it will (by default)
// have a reference to the LView of the current view. The
// element bound to the directive being search lives somewhere
// in the view data. We loop through the nodes and check their
// list of directives for the instance.
let tNode = lView[TVIEW].firstChild;
while (tNode) {
const directiveIndexStart = tNode.directiveStart;
const directiveIndexEnd = tNode.directiveEnd;
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
if (lView[i] === directiveInstance) {
return tNode.index;
}
}
tNode = traverseNextElement(tNode);
}
return -1;
}
/**
* Returns a list of directives applied to a node at a specific index. The list includes
* directives matched by selector and any host directives, but it excludes components.
* Use `getComponentAtNodeIndex` to find the component applied to a node.
*
* @param nodeIndex The node index
* @param lView The target view data
*/
function getDirectivesAtNodeIndex(nodeIndex, lView) {
const tNode = lView[TVIEW].data[nodeIndex];
if (tNode.directiveStart === 0)
return EMPTY_ARRAY;
const results = [];
for (let i = tNode.directiveStart; i < tNode.directiveEnd; i++) {
const directiveInstance = lView[i];
if (!isComponentInstance(directiveInstance)) {
results.push(directiveInstance);
}
}
return results;
}
function getComponentAtNodeIndex(nodeIndex, lView) {
const tNode = lView[TVIEW].data[nodeIndex];
const { directiveStart, componentOffset } = tNode;
return componentOffset > -1 ? lView[directiveStart + componentOffset] : null;
}
/**
* Returns a map of local references (local reference name => element or directive instance) that
* exist on a given element.
*/
function discoverLocalRefs(lView, nodeIndex) {
const tNode = lView[TVIEW].data[nodeIndex];
if (tNode && tNode.localNames) {
const result = {};
let localIndex = tNode.index + 1;
for (let i = 0; i < tNode.localNames.length; i += 2) {
result[tNode.localNames[i]] = lView[localIndex];
localIndex++;
}
return result;
}
return null;
}
/**
* Retrieve the root view from any component or `LView` by walking the parent `LView` until
* reaching the root `LView`.
*
* @param componentOrLView any component or `LView`
*/
function getRootView(componentOrLView) {
ngDevMode && assertDefined(componentOrLView, 'component');
let lView = isLView(componentOrLView) ? componentOrLView : readPatchedLView(componentOrLView);
while (lView && !(lView[FLAGS] & 512 /* LViewFlags.IsRoot */)) {
lView = getLViewParent(lView);
}
ngDevMode && assertLView(lView);
return lView;
}
/**
* Returns the context information associated with the application where the target is situated. It
* does this by walking the parent views until it gets to the root view, then getting the context
* off of that.
*
* @param viewOrComponent the `LView` or component to get the root context for.
*/
function getRootContext(viewOrComponent) {
const rootView = getRootView(viewOrComponent);
ngDevMode &&
assertDefined(rootView[CONTEXT], 'Root view has no context. Perhaps it is disconnected?');
return rootView[CONTEXT];
}
/**
* Gets the first `LContainer` in the LView or `null` if none exists.
*/
function getFirstLContainer(lView) {
return getNearestLContainer(lView[CHILD_HEAD]);
}
/**
* Gets the next `LContainer` that is a sibling of the given container.
*/
function getNextLContainer(container) {
return getNearestLContainer(container[NEXT]);
}
function getNearestLContainer(viewOrContainer) {
while (viewOrContainer !== null && !isLContainer(viewOrContainer)) {
viewOrContainer = viewOrContainer[NEXT];
}
return viewOrContainer;
}
/**
* Retrieves the component instance associated with a given DOM element.
*
* @usageNotes
* Given the following DOM structure:
*
* ```html
*
*
*
*
*
* ```
*
* Calling `getComponent` on `` will return the instance of `ChildComponent`
* associated with this DOM element.
*
* Calling the function on `` will return the `MyApp` instance.
*
*
* @param element DOM element from which the component should be retrieved.
* @returns Component instance associated with the element or `null` if there
* is no component associated with it.
*
* @publicApi
* @globalApi ng
*/
function getComponent$1(element) {
ngDevMode && assertDomElement(element);
const context = getLContext(element);
if (context === null)
return null;
if (context.component === undefined) {
const lView = context.lView;
if (lView === null) {
return null;
}
context.component = getComponentAtNodeIndex(context.nodeIndex, lView);
}
return context.component;
}
/**
* If inside an embedded view (e.g. `*ngIf` or `*ngFor`), retrieves the context of the embedded
* view that the element is part of. Otherwise retrieves the instance of the component whose view
* owns the element (in this case, the result is the same as calling `getOwningComponent`).
*
* @param element Element for which to get the surrounding component instance.
* @returns Instance of the component that is around the element or null if the element isn't
* inside any component.
*
* @publicApi
* @globalApi ng
*/
function getContext(element) {
assertDomElement(element);
const context = getLContext(element);
const lView = context ? context.lView : null;
return lView === null ? null : lView[CONTEXT];
}
/**
* Retrieves the component instance whose view contains the DOM element.
*
* For example, if `` is used in the template of ``
* (i.e. a `ViewChild` of ``), calling `getOwningComponent` on ``
* would return ``.
*
* @param elementOrDir DOM element, component or directive instance
* for which to retrieve the root components.
* @returns Component instance whose view owns the DOM element or null if the element is not
* part of a component view.
*
* @publicApi
* @globalApi ng
*/
function getOwningComponent(elementOrDir) {
const context = getLContext(elementOrDir);
let lView = context ? context.lView : null;
if (lView === null)
return null;
let parent;
while (lView[TVIEW].type === 2 /* TViewType.Embedded */ && (parent = getLViewParent(lView))) {
lView = parent;
}
return lView[FLAGS] & 512 /* LViewFlags.IsRoot */ ? null : lView[CONTEXT];
}
/**
* Retrieves all root components associated with a DOM element, directive or component instance.
* Root components are those which have been bootstrapped by Angular.
*
* @param elementOrDir DOM element, component or directive instance
* for which to retrieve the root components.
* @returns Root components associated with the target object.
*
* @publicApi
* @globalApi ng
*/
function getRootComponents(elementOrDir) {
const lView = readPatchedLView(elementOrDir);
return lView !== null ? [getRootContext(lView)] : [];
}
/**
* Retrieves an `Injector` associated with an element, component or directive instance.
*
* @param elementOrDir DOM element, component or directive instance for which to
* retrieve the injector.
* @returns Injector associated with the element, component or directive instance.
*
* @publicApi
* @globalApi ng
*/
function getInjector(elementOrDir) {
const context = getLContext(elementOrDir);
const lView = context ? context.lView : null;
if (lView === null)
return Injector.NULL;
const tNode = lView[TVIEW].data[context.nodeIndex];
return new NodeInjector(tNode, lView);
}
/**
* Retrieve a set of injection tokens at a given DOM node.
*
* @param element Element for which the injection tokens should be retrieved.
*/
function getInjectionTokens(element) {
const context = getLContext(element);
const lView = context ? context.lView : null;
if (lView === null)
return [];
const tView = lView[TVIEW];
const tNode = tView.data[context.nodeIndex];
const providerTokens = [];
const startIndex = tNode.providerIndexes & 1048575 /* TNodeProviderIndexes.ProvidersStartIndexMask */;
const endIndex = tNode.directiveEnd;
for (let i = startIndex; i < endIndex; i++) {
let value = tView.data[i];
if (isDirectiveDefHack(value)) {
// The fact that we sometimes store Type and sometimes DirectiveDef in this location is a
// design flaw. We should always store same type so that we can be monomorphic. The issue
// is that for Components/Directives we store the def instead the type. The correct behavior
// is that we should always be storing injectable type in this location.
value = value.type;
}
providerTokens.push(value);
}
return providerTokens;
}
/**
* Retrieves directive instances associated with a given DOM node. Does not include
* component instances.
*
* @usageNotes
* Given the following DOM structure:
*
* ```html
*
*
*
*
* ```
*
* Calling `getDirectives` on `