) must have projection slots defined.');
}
function assertParentView(lView, errMessage) {
assertDefined(lView, errMessage || 'Component views should always have a parent view (component\'s host view)');
}
/**
* This is a basic sanity check that the `injectorIndex` seems to point to what looks like a
* NodeInjector data structure.
*
* @param lView `LView` which should be checked.
* @param injectorIndex index into the `LView` where the `NodeInjector` is expected.
*/
function assertNodeInjector(lView, injectorIndex) {
assertIndexInExpandoRange(lView, injectorIndex);
assertIndexInExpandoRange(lView, injectorIndex + 8 /* NodeInjectorOffset.PARENT */);
assertNumber(lView[injectorIndex + 0], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 1], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 2], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 3], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 4], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 5], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 6], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 7], 'injectorIndex should point to a bloom filter');
assertNumber(lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */], 'injectorIndex should point to parent injector');
}
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;
}
/**
* Symbol used to tell `Signal`s apart from other functions.
*
* This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.
*/
const SIGNAL = /* @__PURE__ */ Symbol('SIGNAL');
/**
* Checks if the given `value` is a reactive `Signal`.
*
* @developerPreview
*/
function isSignal(value) {
return typeof value === 'function' && value[SIGNAL] !== undefined;
}
/**
* The default equality function used for `signal` and `computed`, which treats objects and arrays
* as never equal, and all other primitive values using identity semantics.
*
* This allows signals to hold non-primitive values (arrays, objects, other collections) and still
* propagate change notification upon explicit mutation without identity change.
*
* @developerPreview
*/
function defaultEquals(a, b) {
// `Object.is` compares two values using identity semantics which is desired behavior for
// primitive values. If `Object.is` determines two values to be equal we need to make sure that
// those don't represent objects (we want to make sure that 2 objects are always considered
// "unequal"). The null check is needed for the special case of JavaScript reporting null values
// as objects (`typeof null === 'object'`).
return (a === null || typeof a !== 'object') && Object.is(a, b);
}
// Required as the signals library is in a separate package, so we need to explicitly ensure the
/**
* The currently active consumer `ReactiveNode`, if running code in a reactive context.
*
* Change this via `setActiveConsumer`.
*/
let activeConsumer = null;
let inNotificationPhase = false;
function setActiveConsumer(consumer) {
const prev = activeConsumer;
activeConsumer = consumer;
return prev;
}
const REACTIVE_NODE = {
version: 0,
dirty: false,
producerNode: undefined,
producerLastReadVersion: undefined,
producerIndexOfThis: undefined,
nextProducerIndex: 0,
liveConsumerNode: undefined,
liveConsumerIndexOfThis: undefined,
consumerAllowSignalWrites: false,
consumerIsAlwaysLive: false,
producerMustRecompute: () => false,
producerRecomputeValue: () => { },
consumerMarkedDirty: () => { },
};
/**
* Called by implementations when a producer's signal is read.
*/
function producerAccessed(node) {
if (inNotificationPhase) {
throw new Error(typeof ngDevMode !== 'undefined' && ngDevMode ?
`Assertion error: signal read during notification phase` :
'');
}
if (activeConsumer === null) {
// Accessed outside of a reactive context, so nothing to record.
return;
}
// This producer is the `idx`th dependency of `activeConsumer`.
const idx = activeConsumer.nextProducerIndex++;
assertConsumerNode(activeConsumer);
if (idx < activeConsumer.producerNode.length && activeConsumer.producerNode[idx] !== node) {
// There's been a change in producers since the last execution of `activeConsumer`.
// `activeConsumer.producerNode[idx]` holds a stale dependency which will be be removed and
// replaced with `this`.
//
// If `activeConsumer` isn't live, then this is a no-op, since we can replace the producer in
// `activeConsumer.producerNode` directly. However, if `activeConsumer` is live, then we need
// to remove it from the stale producer's `liveConsumer`s.
if (consumerIsLive(activeConsumer)) {
const staleProducer = activeConsumer.producerNode[idx];
producerRemoveLiveConsumerAtIndex(staleProducer, activeConsumer.producerIndexOfThis[idx]);
// At this point, the only record of `staleProducer` is the reference at
// `activeConsumer.producerNode[idx]` which will be overwritten below.
}
}
if (activeConsumer.producerNode[idx] !== node) {
// We're a new dependency of the consumer (at `idx`).
activeConsumer.producerNode[idx] = node;
// If the active consumer is live, then add it as a live consumer. If not, then use 0 as a
// placeholder value.
activeConsumer.producerIndexOfThis[idx] =
consumerIsLive(activeConsumer) ? producerAddLiveConsumer(node, activeConsumer, idx) : 0;
}
activeConsumer.producerLastReadVersion[idx] = node.version;
}
/**
* Ensure this producer's `version` is up-to-date.
*/
function producerUpdateValueVersion(node) {
if (consumerIsLive(node) && !node.dirty) {
// A live consumer will be marked dirty by producers, so a clean state means that its version
// is guaranteed to be up-to-date.
return;
}
if (!node.producerMustRecompute(node) && !consumerPollProducersForChange(node)) {
// None of our producers report a change since the last time they were read, so no
// recomputation of our value is necessary, and we can consider ourselves clean.
node.dirty = false;
return;
}
node.producerRecomputeValue(node);
// After recomputing the value, we're no longer dirty.
node.dirty = false;
}
/**
* Propagate a dirty notification to live consumers of this producer.
*/
function producerNotifyConsumers(node) {
if (node.liveConsumerNode === undefined) {
return;
}
// Prevent signal reads when we're updating the graph
const prev = inNotificationPhase;
inNotificationPhase = true;
try {
for (const consumer of node.liveConsumerNode) {
if (!consumer.dirty) {
consumerMarkDirty(consumer);
}
}
}
finally {
inNotificationPhase = prev;
}
}
/**
* Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,
* based on the current consumer context.
*/
function producerUpdatesAllowed() {
return activeConsumer?.consumerAllowSignalWrites !== false;
}
function consumerMarkDirty(node) {
node.dirty = true;
producerNotifyConsumers(node);
node.consumerMarkedDirty?.(node);
}
/**
* Prepare this consumer to run a computation in its reactive context.
*
* Must be called by subclasses which represent reactive computations, before those computations
* begin.
*/
function consumerBeforeComputation(node) {
node && (node.nextProducerIndex = 0);
return setActiveConsumer(node);
}
/**
* Finalize this consumer's state after a reactive computation has run.
*
* Must be called by subclasses which represent reactive computations, after those computations
* have finished.
*/
function consumerAfterComputation(node, prevConsumer) {
setActiveConsumer(prevConsumer);
if (!node || node.producerNode === undefined || node.producerIndexOfThis === undefined ||
node.producerLastReadVersion === undefined) {
return;
}
if (consumerIsLive(node)) {
// For live consumers, we need to remove the producer -> consumer edge for any stale producers
// which weren't dependencies after the recomputation.
for (let i = node.nextProducerIndex; i < node.producerNode.length; i++) {
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
}
}
// Truncate the producer tracking arrays.
// Perf note: this is essentially truncating the length to `node.nextProducerIndex`, but
// benchmarking has shown that individual pop operations are faster.
while (node.producerNode.length > node.nextProducerIndex) {
node.producerNode.pop();
node.producerLastReadVersion.pop();
node.producerIndexOfThis.pop();
}
}
/**
* Determine whether this consumer has any dependencies which have changed since the last time
* they were read.
*/
function consumerPollProducersForChange(node) {
assertConsumerNode(node);
// Poll producers for change.
for (let i = 0; i < node.producerNode.length; i++) {
const producer = node.producerNode[i];
const seenVersion = node.producerLastReadVersion[i];
// First check the versions. A mismatch means that the producer's value is known to have
// changed since the last time we read it.
if (seenVersion !== producer.version) {
return true;
}
// The producer's version is the same as the last time we read it, but it might itself be
// stale. Force the producer to recompute its version (calculating a new value if necessary).
producerUpdateValueVersion(producer);
// Now when we do this check, `producer.version` is guaranteed to be up to date, so if the
// versions still match then it has not changed since the last time we read it.
if (seenVersion !== producer.version) {
return true;
}
}
return false;
}
/**
* Disconnect this consumer from the graph.
*/
function consumerDestroy(node) {
assertConsumerNode(node);
if (consumerIsLive(node)) {
// Drop all connections from the graph to this node.
for (let i = 0; i < node.producerNode.length; i++) {
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
}
}
// Truncate all the arrays to drop all connection from this node to the graph.
node.producerNode.length = node.producerLastReadVersion.length = node.producerIndexOfThis.length =
0;
if (node.liveConsumerNode) {
node.liveConsumerNode.length = node.liveConsumerIndexOfThis.length = 0;
}
}
/**
* Add `consumer` as a live consumer of this node.
*
* Note that this operation is potentially transitive. If this node becomes live, then it becomes
* a live consumer of all of its current producers.
*/
function producerAddLiveConsumer(node, consumer, indexOfThis) {
assertProducerNode(node);
assertConsumerNode(node);
if (node.liveConsumerNode.length === 0) {
// When going from 0 to 1 live consumers, we become a live consumer to our producers.
for (let i = 0; i < node.producerNode.length; i++) {
node.producerIndexOfThis[i] = producerAddLiveConsumer(node.producerNode[i], node, i);
}
}
node.liveConsumerIndexOfThis.push(indexOfThis);
return node.liveConsumerNode.push(consumer) - 1;
}
/**
* Remove the live consumer at `idx`.
*/
function producerRemoveLiveConsumerAtIndex(node, idx) {
assertProducerNode(node);
assertConsumerNode(node);
if (typeof ngDevMode !== 'undefined' && ngDevMode && idx >= node.liveConsumerNode.length) {
throw new Error(`Assertion error: active consumer index ${idx} is out of bounds of ${node.liveConsumerNode.length} consumers)`);
}
if (node.liveConsumerNode.length === 1) {
// When removing the last live consumer, we will no longer be live. We need to remove
// ourselves from our producers' tracking (which may cause consumer-producers to lose
// liveness as well).
for (let i = 0; i < node.producerNode.length; i++) {
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
}
}
// Move the last value of `liveConsumers` into `idx`. Note that if there's only a single
// live consumer, this is a no-op.
const lastIdx = node.liveConsumerNode.length - 1;
node.liveConsumerNode[idx] = node.liveConsumerNode[lastIdx];
node.liveConsumerIndexOfThis[idx] = node.liveConsumerIndexOfThis[lastIdx];
// Truncate the array.
node.liveConsumerNode.length--;
node.liveConsumerIndexOfThis.length--;
// If the index is still valid, then we need to fix the index pointer from the producer to this
// consumer, and update it from `lastIdx` to `idx` (accounting for the move above).
if (idx < node.liveConsumerNode.length) {
const idxProducer = node.liveConsumerIndexOfThis[idx];
const consumer = node.liveConsumerNode[idx];
assertConsumerNode(consumer);
consumer.producerIndexOfThis[idxProducer] = idx;
}
}
function consumerIsLive(node) {
return node.consumerIsAlwaysLive || (node?.liveConsumerNode?.length ?? 0) > 0;
}
function assertConsumerNode(node) {
node.producerNode ??= [];
node.producerIndexOfThis ??= [];
node.producerLastReadVersion ??= [];
}
function assertProducerNode(node) {
node.liveConsumerNode ??= [];
node.liveConsumerIndexOfThis ??= [];
}
/**
* Create a computed `Signal` which derives a reactive value from an expression.
*
* @developerPreview
*/
function computed(computation, options) {
const node = Object.create(COMPUTED_NODE);
node.computation = computation;
options?.equal && (node.equal = options.equal);
const computed = () => {
// Check if the value needs updating before returning it.
producerUpdateValueVersion(node);
// Record that someone looked at this signal.
producerAccessed(node);
if (node.value === ERRORED) {
throw node.error;
}
return node.value;
};
computed[SIGNAL] = node;
return computed;
}
/**
* A dedicated symbol used before a computed value has been calculated for the first time.
* Explicitly typed as `any` so we can use it as signal's value.
*/
const UNSET = /* @__PURE__ */ Symbol('UNSET');
/**
* A dedicated symbol used in place of a computed signal value to indicate that a given computation
* is in progress. Used to detect cycles in computation chains.
* Explicitly typed as `any` so we can use it as signal's value.
*/
const COMPUTING = /* @__PURE__ */ Symbol('COMPUTING');
/**
* A dedicated symbol used in place of a computed signal value to indicate that a given computation
* failed. The thrown error is cached until the computation gets dirty again.
* Explicitly typed as `any` so we can use it as signal's value.
*/
const ERRORED = /* @__PURE__ */ Symbol('ERRORED');
// 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 COMPUTED_NODE = /* @__PURE__ */ (() => {
return {
...REACTIVE_NODE,
value: UNSET,
dirty: true,
error: null,
equal: defaultEquals,
producerMustRecompute(node) {
// Force a recomputation if there's no current value, or if the current value is in the
// process of being calculated (which should throw an error).
return node.value === UNSET || node.value === COMPUTING;
},
producerRecomputeValue(node) {
if (node.value === COMPUTING) {
// Our computation somehow led to a cyclic read of itself.
throw new Error('Detected cycle in computations.');
}
const oldValue = node.value;
node.value = COMPUTING;
const prevConsumer = consumerBeforeComputation(node);
let newValue;
try {
newValue = node.computation();
}
catch (err) {
newValue = ERRORED;
node.error = err;
}
finally {
consumerAfterComputation(node, prevConsumer);
}
if (oldValue !== UNSET && oldValue !== ERRORED && newValue !== ERRORED &&
node.equal(oldValue, newValue)) {
// No change to `valueVersion` - old and new values are
// semantically equivalent.
node.value = oldValue;
return;
}
node.value = newValue;
node.version++;
},
};
})();
function defaultThrowError() {
throw new Error();
}
let throwInvalidWriteToSignalErrorFn = defaultThrowError;
function throwInvalidWriteToSignalError() {
throwInvalidWriteToSignalErrorFn();
}
function setThrowInvalidWriteToSignalError(fn) {
throwInvalidWriteToSignalErrorFn = fn;
}
/**
* If set, called after `WritableSignal`s are updated.
*
* This hook can be used to achieve various effects, such as running effects synchronously as part
* of setting a signal.
*/
let postSignalSetFn = null;
/**
* Create a `Signal` that can be set or updated directly.
*
* @developerPreview
*/
function signal(initialValue, options) {
const node = Object.create(SIGNAL_NODE);
node.value = initialValue;
options?.equal && (node.equal = options.equal);
function signalFn() {
producerAccessed(node);
return node.value;
}
signalFn.set = signalSetFn;
signalFn.update = signalUpdateFn;
signalFn.mutate = signalMutateFn;
signalFn.asReadonly = signalAsReadonlyFn;
signalFn[SIGNAL] = node;
return signalFn;
}
function setPostSignalSetFn(fn) {
const prev = postSignalSetFn;
postSignalSetFn = fn;
return prev;
}
// 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 SIGNAL_NODE = /* @__PURE__ */ (() => {
return {
...REACTIVE_NODE,
equal: defaultEquals,
readonlyFn: undefined,
};
})();
function signalValueChanged(node) {
node.version++;
producerNotifyConsumers(node);
postSignalSetFn?.();
}
function signalSetFn(newValue) {
const node = this[SIGNAL];
if (!producerUpdatesAllowed()) {
throwInvalidWriteToSignalError();
}
if (!node.equal(node.value, newValue)) {
node.value = newValue;
signalValueChanged(node);
}
}
function signalUpdateFn(updater) {
if (!producerUpdatesAllowed()) {
throwInvalidWriteToSignalError();
}
signalSetFn.call(this, updater(this[SIGNAL].value));
}
function signalMutateFn(mutator) {
const node = this[SIGNAL];
if (!producerUpdatesAllowed()) {
throwInvalidWriteToSignalError();
}
// Mutate bypasses equality checks as it's by definition changing the value.
mutator(node.value);
signalValueChanged(node);
}
function signalAsReadonlyFn() {
const node = this[SIGNAL];
if (node.readonlyFn === undefined) {
const readonlyFn = () => this();
readonlyFn[SIGNAL] = node;
node.readonlyFn = readonlyFn;
}
return node.readonlyFn;
}
/**
* Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function
* can, optionally, return a value.
*
* @developerPreview
*/
function untracked(nonReactiveReadsFn) {
const prevConsumer = setActiveConsumer(null);
// We are not trying to catch any particular errors here, just making sure that the consumers
// stack is restored in case of errors.
try {
return nonReactiveReadsFn();
}
finally {
setActiveConsumer(prevConsumer);
}
}
function watch(fn, schedule, allowSignalWrites) {
const node = Object.create(WATCH_NODE);
if (allowSignalWrites) {
node.consumerAllowSignalWrites = true;
}
node.fn = fn;
node.schedule = schedule;
const registerOnCleanup = (cleanupFn) => {
node.cleanupFn = cleanupFn;
};
const run = () => {
node.dirty = false;
if (node.hasRun && !consumerPollProducersForChange(node)) {
return;
}
node.hasRun = true;
const prevConsumer = consumerBeforeComputation(node);
try {
node.cleanupFn();
node.cleanupFn = NOOP_CLEANUP_FN;
node.fn(registerOnCleanup);
}
finally {
consumerAfterComputation(node, prevConsumer);
}
};
node.ref = {
notify: () => consumerMarkDirty(node),
run,
cleanup: () => node.cleanupFn(),
};
return node.ref;
}
const NOOP_CLEANUP_FN = () => { };
// 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 WATCH_NODE = /* @__PURE__ */ (() => {
return {
...REACTIVE_NODE,
consumerIsAlwaysLive: true,
consumerAllowSignalWrites: false,
consumerMarkedDirty: (node) => {
node.schedule(node.ref);
},
hasRun: false,
cleanupFn: NOOP_CLEANUP_FN,
};
})();
function setAlternateWeakRefImpl(impl) {
// TODO: remove this function
}
/**
* 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;
}
}
/**
* 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, 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);
instance[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 DESCENDANT_VIEWS_TO_REFRESH counters of
* parents.
*/
function markViewForRefresh(lView) {
if ((lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) === 0) {
lView[FLAGS] |= 1024 /* LViewFlags.RefreshView */;
updateViewsToRefresh(lView, 1);
}
}
/**
* Removes the `RefreshView` flag from the lView and updates DESCENDANT_VIEWS_TO_REFRESH counters of
* parents.
*/
function clearViewRefreshFlag(lView) {
if (lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) {
lView[FLAGS] &= ~1024 /* LViewFlags.RefreshView */;
updateViewsToRefresh(lView, -1);
}
}
/**
* Updates the `DESCENDANT_VIEWS_TO_REFRESH` counter on the parents of the `LView` as well as the
* parents above that whose
* 1. counter goes from 0 to 1, indicating that there is a new child that has a view to refresh
* or
* 2. counter goes from 1 to 0, indicating there are no more descendant views to refresh
*/
function updateViewsToRefresh(lView, amount) {
let parent = lView[PARENT];
if (parent === null) {
return;
}
parent[DESCENDANT_VIEWS_TO_REFRESH] += amount;
let viewOrContainer = parent;
parent = parent[PARENT];
while (parent !== null &&
((amount === 1 && viewOrContainer[DESCENDANT_VIEWS_TO_REFRESH] === 1) ||
(amount === -1 && viewOrContainer[DESCENDANT_VIEWS_TO_REFRESH] === 0))) {
parent[DESCENDANT_VIEWS_TO_REFRESH] += amount;
viewOrContainer = parent;
parent = parent[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);
}
}
const instructionState = {
lFrame: createLFrame(null),
bindingsEnabled: true,
skipHydrationRootTNode: null,
};
/**
* 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 _isInCheckNoChangesMode = 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 _isInCheckNoChangesMode;
}
function setIsInCheckNoChangesMode(mode) {
!ngDevMode && throwError('Must never be called in production mode');
_isInCheckNoChangesMode = 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];
}
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;
}
/**
* 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 &= 8191 /* 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(null);
try {
hook.call(directive);
}
finally {
setActiveConsumer(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] >> 13 /* 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] += 8192 /* 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;
}
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
const unusedValueExportToPlacateAjd$2 = 1;
/**
* 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');
return text.length > 0 ? text.substring(1) : text;
}
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
const unusedValueExportToPlacateAjd$1 = 1;
/**
* 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 */)) {
throwError(`Expected TNodeType to have only a single type selected, but got ${toTNodeTypeAsString(type)}.`);
}
}
/// Parent Injector Utils ///////////////////////////////////////////////////////////////
function hasParentInjector(parentLocation) {
return parentLocation !== NO_PARENT_INJECTOR;
}
function getParentInjectorIndex(parentLocation) {
ngDevMode && assertNumber(parentLocation, 'Number expected');
ngDevMode && assertNotEqual(parentLocation, -1, 'Not a valid state.');
const parentInjectorIndex = parentLocation & 32767 /* RelativeInjectorLocationFlags.InjectorIndexMask */;
ngDevMode &&
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;
}
/**
* 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$1];
// 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);
}
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;
});
}
/**
* Attribute decorator and metadata.
*
* @Annotation
* @publicApi
*/
const Attribute = makeParamDecorator('Attribute', (attributeName) => ({ attributeName, __NG_ELEMENT_ID__: () => ɵɵinjectAttribute(attributeName) }));
// Stores the default value of `emitDistinctChangesOnly` when the `emitDistinctChangesOnly` is not
// explicitly set.
const emitDistinctChangesOnlyDefaultValue = true;
/**
* Base class for query metadata.
*
* @see {@link ContentChildren}
* @see {@link ContentChild}
* @see {@link ViewChildren}
* @see {@link ViewChild}
*
* @publicApi
*/
class Query {
}
/**
* ContentChildren decorator and metadata.
*
*
* @Annotation
* @publicApi
*/
const ContentChildren = makePropDecorator('ContentChildren', (selector, data = {}) => ({
selector,
first: false,
isViewQuery: false,
descendants: false,
emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue,
...data
}), Query);
/**
* ContentChild decorator and metadata.
*
*
* @Annotation
*
* @publicApi
*/
const ContentChild = makePropDecorator('ContentChild', (selector, data = {}) => ({ selector, first: true, isViewQuery: false, descendants: true, ...data }), Query);
/**
* ViewChildren decorator and metadata.
*
* @Annotation
* @publicApi
*/
const ViewChildren = makePropDecorator('ViewChildren', (selector, data = {}) => ({
selector,
first: false,
isViewQuery: true,
descendants: true,
emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue,
...data
}), Query);
/**
* ViewChild decorator and metadata.
*
* @Annotation
* @publicApi
*/
const ViewChild = makePropDecorator('ViewChild', (selector, data) => ({ selector, first: true, isViewQuery: true, descendants: true, ...data }), Query);
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');
}
}
/**
* @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';
}
/**
* 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);
}
/*
* #########################
* 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;
}
/**
* Inject decorator and metadata.
*
* @Annotation
* @publicApi
*/
const Inject = attachInjectFlag(
// Disable tslint because `DecoratorFlags` is a const enum which gets inlined.
// tslint:disable-next-line: no-toplevel-property-access
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 */);
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;
}
/**
* Used to resolve resource URLs on `@Component` when used with JIT compilation.
*
* Example:
* ```
* @Component({
* selector: 'my-comp',
* templateUrl: 'my-comp.html', // This requires asynchronous resolution
* })
* class MyComponent{
* }
*
* // Calling `renderComponent` will fail because `renderComponent` is a synchronous process
* // and `MyComponent`'s `@Component.templateUrl` needs to be resolved asynchronously.
*
* // Calling `resolveComponentResources()` will resolve `@Component.templateUrl` into
* // `@Component.template`, which allows `renderComponent` to proceed in a synchronous manner.
*
* // Use browser's `fetch()` function as the default resource resolution strategy.
* resolveComponentResources(fetch).then(() => {
* // After resolution all URLs have been converted into `template` strings.
* renderComponent(MyComponent);
* });
*
* ```
*
* NOTE: In AOT the resolution happens during compilation, and so there should be no need
* to call this method outside JIT mode.
*
* @param resourceResolver a function which is responsible for returning a `Promise` to the
* contents of the resolved URL. Browser's `fetch()` method is a good default implementation.
*/
function resolveComponentResources(resourceResolver) {
// Store all promises which are fetching the resources.
const componentResolved = [];
// Cache so that we don't fetch the same resource more than once.
const urlMap = new Map();
function cachedResourceResolve(url) {
let promise = urlMap.get(url);
if (!promise) {
const resp = resourceResolver(url);
urlMap.set(url, promise = resp.then(unwrapResponse));
}
return promise;
}
componentResourceResolutionQueue.forEach((component, type) => {
const promises = [];
if (component.templateUrl) {
promises.push(cachedResourceResolve(component.templateUrl).then((template) => {
component.template = template;
}));
}
const styleUrls = component.styleUrls;
const styles = component.styles || (component.styles = []);
const styleOffset = component.styles.length;
styleUrls && styleUrls.forEach((styleUrl, index) => {
styles.push(''); // pre-allocate array.
promises.push(cachedResourceResolve(styleUrl).then((style) => {
styles[styleOffset + index] = style;
styleUrls.splice(styleUrls.indexOf(styleUrl), 1);
if (styleUrls.length == 0) {
component.styleUrls = undefined;
}
}));
});
const fullyResolved = Promise.all(promises).then(() => componentDefResolved(type));
componentResolved.push(fullyResolved);
});
clearResolutionOfComponentResourcesQueue();
return Promise.all(componentResolved).then(() => undefined);
}
let componentResourceResolutionQueue = new Map();
// Track when existing ɵcmp for a Type is waiting on resources.
const componentDefPendingResolution = new Set();
function maybeQueueResolutionOfComponentResources(type, metadata) {
if (componentNeedsResolution(metadata)) {
componentResourceResolutionQueue.set(type, metadata);
componentDefPendingResolution.add(type);
}
}
function isComponentDefPendingResolution(type) {
return componentDefPendingResolution.has(type);
}
function componentNeedsResolution(component) {
return !!((component.templateUrl && !component.hasOwnProperty('template')) ||
component.styleUrls && component.styleUrls.length);
}
function clearResolutionOfComponentResourcesQueue() {
const old = componentResourceResolutionQueue;
componentResourceResolutionQueue = new Map();
return old;
}
function restoreComponentResolutionQueue(queue) {
componentDefPendingResolution.clear();
queue.forEach((_, type) => componentDefPendingResolution.add(type));
componentResourceResolutionQueue = queue;
}
function isComponentResourceResolutionQueueEmpty() {
return componentResourceResolutionQueue.size === 0;
}
function unwrapResponse(response) {
return typeof response == 'string' ? response : response.text();
}
function componentDefResolved(type) {
componentDefPendingResolution.delete(type);
}
/**
* Map of module-id to the corresponding NgModule.
*/
const modules = new Map();
/**
* Whether to check for duplicate NgModule registrations.
*
* This can be disabled for testing.
*/
let checkForDuplicateNgModules = true;
function assertSameOrNotExisting(id, type, incoming) {
if (type && type !== incoming && checkForDuplicateNgModules) {
throw new Error(`Duplicate module registered for ${id} - ${stringify(type)} vs ${stringify(type.name)}`);
}
}
/**
* Adds the given NgModule type to Angular's NgModule registry.
*
* This is generated as a side-effect of NgModule compilation. Note that the `id` is passed in
* explicitly and not read from the NgModule definition. This is for two reasons: it avoids a
* megamorphic read, and in JIT there's a chicken-and-egg problem where the NgModule may not be
* fully resolved when it's registered.
*
* @codeGenApi
*/
function registerNgModuleType(ngModuleType, id) {
const existing = modules.get(id) || null;
assertSameOrNotExisting(id, existing, ngModuleType);
modules.set(id, ngModuleType);
}
function clearModulesForTest() {
modules.clear();
}
function getRegisteredNgModuleType(id) {
return modules.get(id);
}
/**
* Control whether the NgModule registration system enforces that each NgModule type registered has
* a unique id.
*
* This is useful for testing as the NgModule registry cannot be properly reset between tests with
* Angular's current API.
*/
function setAllowDuplicateNgModuleIdsForTest(allowDuplicates) {
checkForDuplicateNgModules = !allowDuplicates;
}
/**
* Defines a schema that allows an NgModule to contain the following:
* - Non-Angular elements named with dash case (`-`).
* - Element properties named with dash case (`-`).
* Dash case is the naming convention for custom elements.
*
* @publicApi
*/
const CUSTOM_ELEMENTS_SCHEMA = {
name: 'custom-elements'
};
/**
* Defines a schema that allows any property on any element.
*
* This schema allows you to ignore the errors related to any unknown elements or properties in a
* template. The usage of this schema is generally discouraged because it prevents useful validation
* and may hide real errors in your template. Consider using the `CUSTOM_ELEMENTS_SCHEMA` instead.
*
* @publicApi
*/
const NO_ERRORS_SCHEMA = {
name: 'no-errors-schema'
};
let shouldThrowErrorOnUnknownElement = false;
/**
* Sets a strict mode for JIT-compiled components to throw an error on unknown elements,
* instead of just logging the error.
* (for AOT-compiled ones this check happens at build time).
*/
function ɵsetUnknownElementStrictMode(shouldThrow) {
shouldThrowErrorOnUnknownElement = shouldThrow;
}
/**
* Gets the current value of the strict mode.
*/
function ɵgetUnknownElementStrictMode() {
return shouldThrowErrorOnUnknownElement;
}
let shouldThrowErrorOnUnknownProperty = false;
/**
* Sets a strict mode for JIT-compiled components to throw an error on unknown properties,
* instead of just logging the error.
* (for AOT-compiled ones this check happens at build time).
*/
function ɵsetUnknownPropertyStrictMode(shouldThrow) {
shouldThrowErrorOnUnknownProperty = shouldThrow;
}
/**
* Gets the current value of the strict mode.
*/
function ɵgetUnknownPropertyStrictMode() {
return shouldThrowErrorOnUnknownProperty;
}
/**
* Validates that the element is known at runtime and produces
* an error if it's not the case.
* This check is relevant for JIT-compiled components (for AOT-compiled
* ones this check happens at build time).
*
* The element is considered known if either:
* - it's a known HTML element
* - it's a known custom element
* - the element matches any directive
* - the element is allowed by one of the schemas
*
* @param element Element to validate
* @param lView An `LView` that represents a current component that is being rendered
* @param tagName Name of the tag to check
* @param schemas Array of schemas
* @param hasDirectives Boolean indicating that the element matches any directive
*/
function validateElementIsKnown(element, lView, tagName, schemas, hasDirectives) {
// If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT
// mode where this check happens at compile time. In JIT mode, `schemas` is always present and
// defined as an array (as an empty array in case `schemas` field is not defined) and we should
// execute the check below.
if (schemas === null)
return;
// If the element matches any directive, it's considered as valid.
if (!hasDirectives && tagName !== null) {
// The element is unknown if it's an instance of HTMLUnknownElement, or it isn't registered
// as a custom element. Note that unknown elements with a dash in their name won't be instances
// of HTMLUnknownElement in browsers that support web components.
const isUnknown =
// Note that we can't check for `typeof HTMLUnknownElement === 'function'` because
// Domino doesn't expose HTMLUnknownElement globally.
(typeof HTMLUnknownElement !== 'undefined' && HTMLUnknownElement &&
element instanceof HTMLUnknownElement) ||
(typeof customElements !== 'undefined' && tagName.indexOf('-') > -1 &&
!customElements.get(tagName));
if (isUnknown && !matchingSchemas(schemas, tagName)) {
const isHostStandalone = isHostComponentStandalone(lView);
const templateLocation = getTemplateLocationDetails(lView);
const schemas = `'${isHostStandalone ? '@Component' : '@NgModule'}.schemas'`;
let message = `'${tagName}' is not a known element${templateLocation}:\n`;
message += `1. If '${tagName}' is an Angular component, then verify that it is ${isHostStandalone ? 'included in the \'@Component.imports\' of this component' :
'a part of an @NgModule where this component is declared'}.\n`;
if (tagName && tagName.indexOf('-') > -1) {
message +=
`2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${schemas} of this component to suppress this message.`;
}
else {
message +=
`2. To allow any element add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
}
if (shouldThrowErrorOnUnknownElement) {
throw new RuntimeError(304 /* RuntimeErrorCode.UNKNOWN_ELEMENT */, message);
}
else {
console.error(formatRuntimeError(304 /* RuntimeErrorCode.UNKNOWN_ELEMENT */, message));
}
}
}
}
/**
* Validates that the property of the element is known at runtime and returns
* false if it's not the case.
* This check is relevant for JIT-compiled components (for AOT-compiled
* ones this check happens at build time).
*
* The property is considered known if either:
* - it's a known property of the element
* - the element is allowed by one of the schemas
* - the property is used for animations
*
* @param element Element to validate
* @param propName Name of the property to check
* @param tagName Name of the tag hosting the property
* @param schemas Array of schemas
*/
function isPropertyValid(element, propName, tagName, schemas) {
// If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT
// mode where this check happens at compile time. In JIT mode, `schemas` is always present and
// defined as an array (as an empty array in case `schemas` field is not defined) and we should
// execute the check below.
if (schemas === null)
return true;
// The property is considered valid if the element matches the schema, it exists on the element,
// or it is synthetic.
if (matchingSchemas(schemas, tagName) || propName in element || isAnimationProp(propName)) {
return true;
}
// Note: `typeof Node` returns 'function' in most browsers, but is undefined with domino.
return typeof Node === 'undefined' || Node === null || !(element instanceof Node);
}
/**
* Logs or throws an error that a property is not supported on an element.
*
* @param propName Name of the invalid property
* @param tagName Name of the tag hosting the property
* @param nodeType Type of the node hosting the property
* @param lView An `LView` that represents a current component
*/
function handleUnknownPropertyError(propName, tagName, nodeType, lView) {
// Special-case a situation when a structural directive is applied to
// an `` element, for example: ``.
// In this case the compiler generates the `ɵɵtemplate` instruction with
// the `null` as the tagName. The directive matching logic at runtime relies
// on this effect (see `isInlineTemplate`), thus using the 'ng-template' as
// a default value of the `tNode.value` is not feasible at this moment.
if (!tagName && nodeType === 4 /* TNodeType.Container */) {
tagName = 'ng-template';
}
const isHostStandalone = isHostComponentStandalone(lView);
const templateLocation = getTemplateLocationDetails(lView);
let message = `Can't bind to '${propName}' since it isn't a known property of '${tagName}'${templateLocation}.`;
const schemas = `'${isHostStandalone ? '@Component' : '@NgModule'}.schemas'`;
const importLocation = isHostStandalone ?
'included in the \'@Component.imports\' of this component' :
'a part of an @NgModule where this component is declared';
if (KNOWN_CONTROL_FLOW_DIRECTIVES.has(propName)) {
// Most likely this is a control flow directive (such as `*ngIf`) used in
// a template, but the directive or the `CommonModule` is not imported.
const correspondingImport = KNOWN_CONTROL_FLOW_DIRECTIVES.get(propName);
message += `\nIf the '${propName}' is an Angular control flow directive, ` +
`please make sure that either the '${correspondingImport}' directive or the 'CommonModule' is ${importLocation}.`;
}
else {
// May be an Angular component, which is not imported/declared?
message += `\n1. If '${tagName}' is an Angular component and it has the ` +
`'${propName}' input, then verify that it is ${importLocation}.`;
// May be a Web Component?
if (tagName && tagName.indexOf('-') > -1) {
message += `\n2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' ` +
`to the ${schemas} of this component to suppress this message.`;
message += `\n3. To allow any property add 'NO_ERRORS_SCHEMA' to ` +
`the ${schemas} of this component.`;
}
else {
// If it's expected, the error can be suppressed by the `NO_ERRORS_SCHEMA` schema.
message += `\n2. To allow any property add 'NO_ERRORS_SCHEMA' to ` +
`the ${schemas} of this component.`;
}
}
reportUnknownPropertyError(message);
}
function reportUnknownPropertyError(message) {
if (shouldThrowErrorOnUnknownProperty) {
throw new RuntimeError(303 /* RuntimeErrorCode.UNKNOWN_BINDING */, message);
}
else {
console.error(formatRuntimeError(303 /* RuntimeErrorCode.UNKNOWN_BINDING */, message));
}
}
/**
* WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`)
* and must **not** be used in production bundles. The function makes megamorphic reads, which might
* be too slow for production mode and also it relies on the constructor function being available.
*
* Gets a reference to the host component def (where a current component is declared).
*
* @param lView An `LView` that represents a current component that is being rendered.
*/
function getDeclarationComponentDef(lView) {
!ngDevMode && throwError('Must never be called in production mode');
const declarationLView = lView[DECLARATION_COMPONENT_VIEW];
const context = declarationLView[CONTEXT];
// Unable to obtain a context.
if (!context)
return null;
return context.constructor ? getComponentDef(context.constructor) : null;
}
/**
* WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`)
* and must **not** be used in production bundles. The function makes megamorphic reads, which might
* be too slow for production mode.
*
* Checks if the current component is declared inside of a standalone component template.
*
* @param lView An `LView` that represents a current component that is being rendered.
*/
function isHostComponentStandalone(lView) {
!ngDevMode && throwError('Must never be called in production mode');
const componentDef = getDeclarationComponentDef(lView);
// Treat host component as non-standalone if we can't obtain the def.
return !!componentDef?.standalone;
}
/**
* WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`)
* and must **not** be used in production bundles. The function makes megamorphic reads, which might
* be too slow for production mode.
*
* Constructs a string describing the location of the host component template. The function is used
* in dev mode to produce error messages.
*
* @param lView An `LView` that represents a current component that is being rendered.
*/
function getTemplateLocationDetails(lView) {
!ngDevMode && throwError('Must never be called in production mode');
const hostComponentDef = getDeclarationComponentDef(lView);
const componentClassName = hostComponentDef?.type?.name;
return componentClassName ? ` (used in the '${componentClassName}' component template)` : '';
}
/**
* The set of known control flow directives and their corresponding imports.
* We use this set to produce a more precises error message with a note
* that the `CommonModule` should also be included.
*/
const KNOWN_CONTROL_FLOW_DIRECTIVES = new Map([
['ngIf', 'NgIf'], ['ngFor', 'NgFor'], ['ngSwitchCase', 'NgSwitchCase'],
['ngSwitchDefault', 'NgSwitchDefault']
]);
/**
* Returns true if the tag name is allowed by specified schemas.
* @param schemas Array of schemas
* @param tagName Name of the tag
*/
function matchingSchemas(schemas, tagName) {
if (schemas !== null) {
for (let i = 0; i < schemas.length; i++) {
const schema = schemas[i];
if (schema === NO_ERRORS_SCHEMA ||
schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) {
return true;
}
}
}
return false;
}
/**
* 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';
/**
* Helper function to check if a given TNode has the 'ngSkipHydration' attribute.
*/
function hasSkipHydrationAttrOnTNode(tNode) {
const SKIP_HYDRATION_ATTR_NAME_LOWER_CASE = SKIP_HYDRATION_ATTR_NAME.toLowerCase();
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.
*
* TODO(akushnir): this function should contain the logic of `hasInSkipHydrationBlockFlag`,
* there is no need to traverse parent nodes when we have a TNode flag (which would also
* make this lookup O(1)).
*/
function isInSkipHydrationBlock(tNode) {
let currentTNode = tNode.parent;
while (currentTNode) {
if (hasSkipHydrationAttrOnTNode(currentTNode)) {
return true;
}
currentTNode = currentTNode.parent;
}
return false;
}
/**
* Flags for renderer-specific style modifiers.
* @publicApi
*/
var RendererStyleFlags2;
(function (RendererStyleFlags2) {
// TODO(misko): This needs to be refactored into a separate file so that it can be imported from
// `node_manipulation.ts` Currently doing the import cause resolution order to change and fails
// the tests. The work around is to have hard coded value in `node_manipulation.ts` for now.
/**
* Marks a style as important.
*/
RendererStyleFlags2[RendererStyleFlags2["Important"] = 1] = "Important";
/**
* Marks a style as using dash case naming (this-is-dash-case).
*/
RendererStyleFlags2[RendererStyleFlags2["DashCase"] = 2] = "DashCase";
})(RendererStyleFlags2 || (RendererStyleFlags2 = {}));
/**
* Disallowed strings in the comment.
*
* see: https://html.spec.whatwg.org/multipage/syntax.html#comments
*/
const COMMENT_DISALLOWED = /^>|^->||--!>|)/g;
const COMMENT_DELIMITER_ESCAPED = '\u200B$1\u200B';
/**
* Escape the content of comment strings so that it can be safely inserted into a comment node.
*
* The issue is that HTML does not specify any way to escape comment end text inside the comment.
* Consider: `" or
* "--!>" at the end. -->`. Above the `"-->"` is meant to be text not an end to the comment. This
* can be created programmatically through DOM APIs. (`` or `--!>`) the
* text it will render normally but it will not cause the HTML parser to close/open the comment.
*
* @param value text to make safe for comment node by escaping the comment open/close character
* sequence.
*/
function escapeCommentText(value) {
return value.replace(COMMENT_DISALLOWED, (text) => text.replace(COMMENT_DELIMITER, COMMENT_DELIMITER_ESCAPED));
}
// 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__';
/**
* 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;
}
let _icuContainerIterate;
/**
* Iterator which provides ability to visit all of the `TIcuContainerNode` root `RNode`s.
*/
function icuContainerIterate(tIcuContainerNode, lView) {
return _icuContainerIterate(tIcuContainerNode, lView);
}
/**
* Ensures that `IcuContainerVisitor`'s implementation is present.
*
* This function is invoked when i18n instruction comes across an ICU. The purpose is to allow the
* bundler to tree shake ICU logic and only load it if ICU instruction is executed.
*/
function ensureIcuContainerVisitorLoaded(loader) {
if (_icuContainerIterate === undefined) {
// Do not inline this function. We want to keep `ensureIcuContainerVisitorLoaded` light, so it
// can be inlined into call-site.
_icuContainerIterate = loader();
}
}
/**
* 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;
}
/**
* 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;
}
/**
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
* being passed as an argument.
*/
function applyToElementOrContainer(action, renderer, parent, lNodeToHandle, beforeNode) {
// If this slot was allocated for a text node dynamically created by i18n, the text node itself
// won't be created until i18nApply() in the update block, so this node should be skipped.
// For more info, see "ICU expressions should work inside an ngTemplateOutlet inside an ngFor"
// in `i18n_spec.ts`.
if (lNodeToHandle != null) {
let lContainer;
let isComponent = false;
// We are expecting an RNode, but in the case of a component or LContainer the `RNode` is
// wrapped in an array which needs to be unwrapped. We need to know if it is a component and if
// it has LContainer so that we can process all of those cases appropriately.
if (isLContainer(lNodeToHandle)) {
lContainer = lNodeToHandle;
}
else if (isLView(lNodeToHandle)) {
isComponent = true;
ngDevMode && assertDefined(lNodeToHandle[HOST], 'HOST must be defined for a component LView');
lNodeToHandle = lNodeToHandle[HOST];
}
const rNode = unwrapRNode(lNodeToHandle);
if (action === 0 /* WalkTNodeTreeAction.Create */ && parent !== null) {
if (beforeNode == null) {
nativeAppendChild(renderer, parent, rNode);
}
else {
nativeInsertBefore(renderer, parent, rNode, beforeNode || null, true);
}
}
else if (action === 1 /* WalkTNodeTreeAction.Insert */ && parent !== null) {
nativeInsertBefore(renderer, parent, rNode, beforeNode || null, true);
}
else if (action === 2 /* WalkTNodeTreeAction.Detach */) {
nativeRemoveNode(renderer, rNode, isComponent);
}
else if (action === 3 /* WalkTNodeTreeAction.Destroy */) {
ngDevMode && ngDevMode.rendererDestroyNode++;
renderer.destroyNode(rNode);
}
if (lContainer != null) {
applyContainer(renderer, action, lContainer, parent, beforeNode);
}
}
}
function createTextNode(renderer, value) {
ngDevMode && ngDevMode.rendererCreateTextNode++;
ngDevMode && ngDevMode.rendererSetText++;
return renderer.createText(value);
}
function updateTextNode(renderer, rNode, value) {
ngDevMode && ngDevMode.rendererSetText++;
renderer.setValue(rNode, value);
}
function createCommentNode(renderer, value) {
ngDevMode && ngDevMode.rendererCreateComment++;
return renderer.createComment(escapeCommentText(value));
}
/**
* Creates a native element from a tag name, using a renderer.
* @param renderer A renderer to use
* @param name the tag name
* @param namespace Optional namespace for element.
* @returns the element created
*/
function createElementNode(renderer, name, namespace) {
ngDevMode && ngDevMode.rendererCreateElement++;
return renderer.createElement(name, namespace);
}
/**
* Removes all DOM elements associated with a view.
*
* Because some root nodes of the view may be containers, we sometimes need
* to propagate deeply into the nested containers to remove all elements in the
* views beneath it.
*
* @param tView The `TView' of the `LView` from which elements should be added or removed
* @param lView The view from which elements should be added or removed
*/
function removeViewFromDOM(tView, lView) {
const renderer = lView[RENDERER];
applyView(tView, lView, renderer, 2 /* WalkTNodeTreeAction.Detach */, null, null);
lView[HOST] = null;
lView[T_HOST] = null;
}
/**
* Adds all DOM elements associated with a view.
*
* Because some root nodes of the view may be containers, we sometimes need
* to propagate deeply into the nested containers to add all elements in the
* views beneath it.
*
* @param tView The `TView' of the `LView` from which elements should be added or removed
* @param parentTNode The `TNode` where the `LView` should be attached to.
* @param renderer Current renderer to use for DOM manipulations.
* @param lView The view from which elements should be added or removed
* @param parentNativeNode The parent `RElement` where it should be inserted into.
* @param beforeNode The node before which elements should be added, if insert mode
*/
function addViewToDOM(tView, parentTNode, renderer, lView, parentNativeNode, beforeNode) {
lView[HOST] = parentNativeNode;
lView[T_HOST] = parentTNode;
applyView(tView, lView, renderer, 1 /* WalkTNodeTreeAction.Insert */, parentNativeNode, beforeNode);
}
/**
* Detach a `LView` from the DOM by detaching its nodes.
*
* @param tView The `TView' of the `LView` to be detached
* @param lView the `LView` to be detached.
*/
function detachViewFromDOM(tView, lView) {
applyView(tView, lView, lView[RENDERER], 2 /* WalkTNodeTreeAction.Detach */, null, null);
}
/**
* Traverses down and up the tree of views and containers to remove listeners and
* call onDestroy callbacks.
*
* Notes:
* - Because it's used for onDestroy calls, it needs to be bottom-up.
* - Must process containers instead of their views to avoid splicing
* when views are destroyed and re-added.
* - Using a while loop because it's faster than recursion
* - Destroy only called on movement to sibling or movement to parent (laterally or up)
*
* @param rootView The view to destroy
*/
function destroyViewTree(rootView) {
// If the view has no children, we can clean it up and return early.
let lViewOrLContainer = rootView[CHILD_HEAD];
if (!lViewOrLContainer) {
return cleanUpView(rootView[TVIEW], rootView);
}
while (lViewOrLContainer) {
let next = null;
if (isLView(lViewOrLContainer)) {
// If LView, traverse down to child.
next = lViewOrLContainer[CHILD_HEAD];
}
else {
ngDevMode && assertLContainer(lViewOrLContainer);
// If container, traverse down to its first LView.
const firstView = lViewOrLContainer[CONTAINER_HEADER_OFFSET];
if (firstView)
next = firstView;
}
if (!next) {
// Only clean up view when moving to the side or up, as destroy hooks
// should be called in order from the bottom up.
while (lViewOrLContainer && !lViewOrLContainer[NEXT] && lViewOrLContainer !== rootView) {
if (isLView(lViewOrLContainer)) {
cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer);
}
lViewOrLContainer = lViewOrLContainer[PARENT];
}
if (lViewOrLContainer === null)
lViewOrLContainer = rootView;
if (isLView(lViewOrLContainer)) {
cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer);
}
next = lViewOrLContainer && lViewOrLContainer[NEXT];
}
lViewOrLContainer = next;
}
}
/**
* Inserts a view into a container.
*
* This adds the view to the container's array of active views in the correct
* position. It also adds the view's elements to the DOM if the container isn't a
* root node of another view (in that case, the view's elements will be added when
* the container's parent view is added later).
*
* @param tView The `TView' of the `LView` to insert
* @param lView The view to insert
* @param lContainer The container into which the view should be inserted
* @param index Which index in the container to insert the child view into
*/
function insertView(tView, lView, lContainer, index) {
ngDevMode && assertLView(lView);
ngDevMode && assertLContainer(lContainer);
const indexInContainer = CONTAINER_HEADER_OFFSET + index;
const containerLength = lContainer.length;
if (index > 0) {
// This is a new view, we need to add it to the children.
lContainer[indexInContainer - 1][NEXT] = lView;
}
if (index < containerLength - CONTAINER_HEADER_OFFSET) {
lView[NEXT] = lContainer[indexInContainer];
addToArray(lContainer, CONTAINER_HEADER_OFFSET + index, lView);
}
else {
lContainer.push(lView);
lView[NEXT] = null;
}
lView[PARENT] = lContainer;
// track views where declaration and insertion points are different
const declarationLContainer = lView[DECLARATION_LCONTAINER];
if (declarationLContainer !== null && lContainer !== declarationLContainer) {
trackMovedView(declarationLContainer, lView);
}
// notify query that a new view has been added
const lQueries = lView[QUERIES];
if (lQueries !== null) {
lQueries.insertView(tView);
}
// Sets the attached flag
lView[FLAGS] |= 128 /* LViewFlags.Attached */;
}
/**
* Track views created from the declaration container (TemplateRef) and inserted into a
* different LContainer.
*/
function trackMovedView(declarationContainer, lView) {
ngDevMode && assertDefined(lView, 'LView required');
ngDevMode && assertLContainer(declarationContainer);
const movedViews = declarationContainer[MOVED_VIEWS];
const insertedLContainer = lView[PARENT];
ngDevMode && assertLContainer(insertedLContainer);
const insertedComponentLView = insertedLContainer[PARENT][DECLARATION_COMPONENT_VIEW];
ngDevMode && assertDefined(insertedComponentLView, 'Missing insertedComponentLView');
const declaredComponentLView = lView[DECLARATION_COMPONENT_VIEW];
ngDevMode && assertDefined(declaredComponentLView, 'Missing declaredComponentLView');
if (declaredComponentLView !== insertedComponentLView) {
// At this point the declaration-component is not same as insertion-component; this means that
// this is a transplanted view. Mark the declared lView as having transplanted views so that
// those views can participate in CD.
declarationContainer[HAS_TRANSPLANTED_VIEWS] = true;
}
if (movedViews === null) {
declarationContainer[MOVED_VIEWS] = [lView];
}
else {
movedViews.push(lView);
}
}
function detachMovedView(declarationContainer, lView) {
ngDevMode && assertLContainer(declarationContainer);
ngDevMode &&
assertDefined(declarationContainer[MOVED_VIEWS], 'A projected view should belong to a non-empty projected views collection');
const movedViews = declarationContainer[MOVED_VIEWS];
const declarationViewIndex = movedViews.indexOf(lView);
const insertionLContainer = lView[PARENT];
ngDevMode && assertLContainer(insertionLContainer);
// If the view was marked for refresh but then detached before it was checked (where the flag
// would be cleared and the counter decremented), we need to update the status here.
clearViewRefreshFlag(lView);
movedViews.splice(declarationViewIndex, 1);
}
/**
* Detaches a view from a container.
*
* This method removes the view from the container's array of active views. It also
* removes the view's elements from the DOM.
*
* @param lContainer The container from which to detach a view
* @param removeIndex The index of the view to detach
* @returns Detached LView instance.
*/
function detachView(lContainer, removeIndex) {
if (lContainer.length <= CONTAINER_HEADER_OFFSET)
return;
const indexInContainer = CONTAINER_HEADER_OFFSET + removeIndex;
const viewToDetach = lContainer[indexInContainer];
if (viewToDetach) {
const declarationLContainer = viewToDetach[DECLARATION_LCONTAINER];
if (declarationLContainer !== null && declarationLContainer !== lContainer) {
detachMovedView(declarationLContainer, viewToDetach);
}
if (removeIndex > 0) {
lContainer[indexInContainer - 1][NEXT] = viewToDetach[NEXT];
}
const removedLView = removeFromArray(lContainer, CONTAINER_HEADER_OFFSET + removeIndex);
removeViewFromDOM(viewToDetach[TVIEW], viewToDetach);
// notify query that a view has been removed
const lQueries = removedLView[QUERIES];
if (lQueries !== null) {
lQueries.detachView(removedLView[TVIEW]);
}
viewToDetach[PARENT] = null;
viewToDetach[NEXT] = null;
// Unsets the attached flag
viewToDetach[FLAGS] &= ~128 /* LViewFlags.Attached */;
}
return viewToDetach;
}
/**
* A standalone function which destroys an LView,
* conducting clean up (e.g. removing listeners, calling onDestroys).
*
* @param tView The `TView' of the `LView` to be destroyed
* @param lView The view to be destroyed.
*/
function destroyLView(tView, lView) {
if (!(lView[FLAGS] & 256 /* LViewFlags.Destroyed */)) {
const renderer = lView[RENDERER];
lView[REACTIVE_TEMPLATE_CONSUMER] && consumerDestroy(lView[REACTIVE_TEMPLATE_CONSUMER]);
lView[REACTIVE_HOST_BINDING_CONSUMER] && consumerDestroy(lView[REACTIVE_HOST_BINDING_CONSUMER]);
if (renderer.destroyNode) {
applyView(tView, lView, renderer, 3 /* WalkTNodeTreeAction.Destroy */, null, null);
}
destroyViewTree(lView);
}
}
/**
* Calls onDestroys hooks for all directives and pipes in a given view and then removes all
* listeners. Listeners are removed as the last step so events delivered in the onDestroys hooks
* can be propagated to @Output listeners.
*
* @param tView `TView` for the `LView` to clean up.
* @param lView The LView to clean up
*/
function cleanUpView(tView, lView) {
if (!(lView[FLAGS] & 256 /* LViewFlags.Destroyed */)) {
// Usually the Attached flag is removed when the view is detached from its parent, however
// if it's a root view, the flag won't be unset hence why we're also removing on destroy.
lView[FLAGS] &= ~128 /* LViewFlags.Attached */;
// Mark the LView as destroyed *before* executing the onDestroy hooks. An onDestroy hook
// runs arbitrary user code, which could include its own `viewRef.destroy()` (or similar). If
// We don't flag the view as destroyed before the hooks, this could lead to an infinite loop.
// This also aligns with the ViewEngine behavior. It also means that the onDestroy hook is
// really more of an "afterDestroy" hook if you think about it.
lView[FLAGS] |= 256 /* LViewFlags.Destroyed */;
executeOnDestroys(tView, lView);
processCleanups(tView, lView);
// For component views only, the local renderer is destroyed at clean up time.
if (lView[TVIEW].type === 1 /* TViewType.Component */) {
ngDevMode && ngDevMode.rendererDestroy++;
lView[RENDERER].destroy();
}
const declarationContainer = lView[DECLARATION_LCONTAINER];
// we are dealing with an embedded view that is still inserted into a container
if (declarationContainer !== null && isLContainer(lView[PARENT])) {
// and this is a projected view
if (declarationContainer !== lView[PARENT]) {
detachMovedView(declarationContainer, lView);
}
// For embedded views still attached to a container: remove query result from this view.
const lQueries = lView[QUERIES];
if (lQueries !== null) {
lQueries.detachView(tView);
}
}
// Unregister the view once everything else has been cleaned up.
unregisterLView(lView);
}
}
/** Removes listeners and unsubscribes from output subscriptions */
function processCleanups(tView, lView) {
const tCleanup = tView.cleanup;
const lCleanup = lView[CLEANUP];
if (tCleanup !== null) {
for (let i = 0; i < tCleanup.length - 1; i += 2) {
if (typeof tCleanup[i] === 'string') {
// This is a native DOM listener. It will occupy 4 entries in the TCleanup array (hence i +=
// 2 at the end of this block).
const targetIdx = tCleanup[i + 3];
ngDevMode && assertNumber(targetIdx, 'cleanup target must be a number');
if (targetIdx >= 0) {
// unregister
lCleanup[targetIdx]();
}
else {
// Subscription
lCleanup[-targetIdx].unsubscribe();
}
i += 2;
}
else {
// This is a cleanup function that is grouped with the index of its context
const context = lCleanup[tCleanup[i + 1]];
tCleanup[i].call(context);
}
}
}
if (lCleanup !== null) {
lView[CLEANUP] = null;
}
const destroyHooks = lView[ON_DESTROY_HOOKS];
if (destroyHooks !== null) {
// Reset the ON_DESTROY_HOOKS array before iterating over it to prevent hooks that unregister
// themselves from mutating the array during iteration.
lView[ON_DESTROY_HOOKS] = null;
for (let i = 0; i < destroyHooks.length; i++) {
const destroyHooksFn = destroyHooks[i];
ngDevMode && assertFunction(destroyHooksFn, 'Expecting destroy hook to be a function.');
destroyHooksFn();
}
}
}
/** Calls onDestroy hooks for this view */
function executeOnDestroys(tView, lView) {
let destroyHooks;
if (tView != null && (destroyHooks = tView.destroyHooks) != null) {
for (let i = 0; i < destroyHooks.length; i += 2) {
const context = lView[destroyHooks[i]];
// Only call the destroy hook if the context has been requested.
if (!(context instanceof NodeInjectorFactory)) {
const toCall = destroyHooks[i + 1];
if (Array.isArray(toCall)) {
for (let j = 0; j < toCall.length; j += 2) {
const callContext = context[toCall[j]];
const hook = toCall[j + 1];
profiler(4 /* ProfilerEvent.LifecycleHookStart */, callContext, hook);
try {
hook.call(callContext);
}
finally {
profiler(5 /* ProfilerEvent.LifecycleHookEnd */, callContext, hook);
}
}
}
else {
profiler(4 /* ProfilerEvent.LifecycleHookStart */, context, toCall);
try {
toCall.call(context);
}
finally {
profiler(5 /* ProfilerEvent.LifecycleHookEnd */, context, toCall);
}
}
}
}
}
}
/**
* Returns a native element if a node can be inserted into the given parent.
*
* There are two reasons why we may not be able to insert a element immediately.
* - Projection: When creating a child content element of a component, we have to skip the
* insertion because the content of a component will be projected.
* `delayed due to projection`
* - Parent container is disconnected: This can happen when we are inserting a view into
* parent container, which itself is disconnected. For example the parent container is part
* of a View which has not be inserted or is made for projection but has not been inserted
* into destination.
*
* @param tView: Current `TView`.
* @param tNode: `TNode` for which we wish to retrieve render parent.
* @param lView: Current `LView`.
*/
function getParentRElement(tView, tNode, lView) {
return getClosestRElement(tView, tNode.parent, lView);
}
/**
* Get closest `RElement` or `null` if it can't be found.
*
* If `TNode` is `TNodeType.Element` => return `RElement` at `LView[tNode.index]` location.
* If `TNode` is `TNodeType.ElementContainer|IcuContain` => return the parent (recursively).
* If `TNode` is `null` then return host `RElement`:
* - return `null` if projection
* - return `null` if parent container is disconnected (we have no parent.)
*
* @param tView: Current `TView`.
* @param tNode: `TNode` for which we wish to retrieve `RElement` (or `null` if host element is
* needed).
* @param lView: Current `LView`.
* @returns `null` if the `RElement` can't be determined at this time (no parent / projection)
*/
function getClosestRElement(tView, tNode, lView) {
let parentTNode = tNode;
// Skip over element and ICU containers as those are represented by a comment node and
// can't be used as a render parent.
while (parentTNode !== null &&
(parentTNode.type & (8 /* TNodeType.ElementContainer */ | 32 /* TNodeType.Icu */))) {
tNode = parentTNode;
parentTNode = tNode.parent;
}
// If the parent tNode is null, then we are inserting across views: either into an embedded view
// or a component view.
if (parentTNode === null) {
// We are inserting a root element of the component view into the component host element and
// it should always be eager.
return lView[HOST];
}
else {
ngDevMode && assertTNodeType(parentTNode, 3 /* TNodeType.AnyRNode */ | 4 /* TNodeType.Container */);
const { componentOffset } = parentTNode;
if (componentOffset > -1) {
ngDevMode && assertTNodeForLView(parentTNode, lView);
const { encapsulation } = tView.data[parentTNode.directiveStart + componentOffset];
// We've got a parent which is an element in the current view. We just need to verify if the
// parent element is not a component. Component's content nodes are not inserted immediately
// because they will be projected, and so doing insert at this point would be wasteful.
// Since the projection would then move it to its final destination. Note that we can't
// make this assumption when using the Shadow DOM, because the native projection placeholders
// ( or ) have to be in place as elements are being inserted.
if (encapsulation === ViewEncapsulation$1.None ||
encapsulation === ViewEncapsulation$1.Emulated) {
return null;
}
}
return getNativeByTNode(parentTNode, lView);
}
}
/**
* Inserts a native node before another native node for a given parent.
* This is a utility function that can be used when native nodes were determined.
*/
function nativeInsertBefore(renderer, parent, child, beforeNode, isMove) {
ngDevMode && ngDevMode.rendererInsertBefore++;
renderer.insertBefore(parent, child, beforeNode, isMove);
}
function nativeAppendChild(renderer, parent, child) {
ngDevMode && ngDevMode.rendererAppendChild++;
ngDevMode && assertDefined(parent, 'parent node must be defined');
renderer.appendChild(parent, child);
}
function nativeAppendOrInsertBefore(renderer, parent, child, beforeNode, isMove) {
if (beforeNode !== null) {
nativeInsertBefore(renderer, parent, child, beforeNode, isMove);
}
else {
nativeAppendChild(renderer, parent, child);
}
}
/** Removes a node from the DOM given its native parent. */
function nativeRemoveChild(renderer, parent, child, isHostElement) {
renderer.removeChild(parent, child, isHostElement);
}
/** Checks if an element is a `` node. */
function isTemplateNode(node) {
return node.tagName === 'TEMPLATE' && node.content !== undefined;
}
/**
* Returns a native parent of a given native node.
*/
function nativeParentNode(renderer, node) {
return renderer.parentNode(node);
}
/**
* Returns a native sibling of a given native node.
*/
function nativeNextSibling(renderer, node) {
return renderer.nextSibling(node);
}
/**
* Find a node in front of which `currentTNode` should be inserted.
*
* This method determines the `RNode` in front of which we should insert the `currentRNode`. This
* takes `TNode.insertBeforeIndex` into account if i18n code has been invoked.
*
* @param parentTNode parent `TNode`
* @param currentTNode current `TNode` (The node which we would like to insert into the DOM)
* @param lView current `LView`
*/
function getInsertInFrontOfRNode(parentTNode, currentTNode, lView) {
return _getInsertInFrontOfRNodeWithI18n(parentTNode, currentTNode, lView);
}
/**
* Find a node in front of which `currentTNode` should be inserted. (Does not take i18n into
* account)
*
* This method determines the `RNode` in front of which we should insert the `currentRNode`. This
* does not take `TNode.insertBeforeIndex` into account.
*
* @param parentTNode parent `TNode`
* @param currentTNode current `TNode` (The node which we would like to insert into the DOM)
* @param lView current `LView`
*/
function getInsertInFrontOfRNodeWithNoI18n(parentTNode, currentTNode, lView) {
if (parentTNode.type & (8 /* TNodeType.ElementContainer */ | 32 /* TNodeType.Icu */)) {
return getNativeByTNode(parentTNode, lView);
}
return null;
}
/**
* Tree shakable boundary for `getInsertInFrontOfRNodeWithI18n` function.
*
* This function will only be set if i18n code runs.
*/
let _getInsertInFrontOfRNodeWithI18n = getInsertInFrontOfRNodeWithNoI18n;
/**
* Tree shakable boundary for `processI18nInsertBefore` function.
*
* This function will only be set if i18n code runs.
*/
let _processI18nInsertBefore;
function setI18nHandling(getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore) {
_getInsertInFrontOfRNodeWithI18n = getInsertInFrontOfRNodeWithI18n;
_processI18nInsertBefore = processI18nInsertBefore;
}
/**
* Appends the `child` native node (or a collection of nodes) to the `parent`.
*
* @param tView The `TView' to be appended
* @param lView The current LView
* @param childRNode The native child (or children) that should be appended
* @param childTNode The TNode of the child element
*/
function appendChild(tView, lView, childRNode, childTNode) {
const parentRNode = getParentRElement(tView, childTNode, lView);
const renderer = lView[RENDERER];
const parentTNode = childTNode.parent || lView[T_HOST];
const anchorNode = getInsertInFrontOfRNode(parentTNode, childTNode, lView);
if (parentRNode != null) {
if (Array.isArray(childRNode)) {
for (let i = 0; i < childRNode.length; i++) {
nativeAppendOrInsertBefore(renderer, parentRNode, childRNode[i], anchorNode, false);
}
}
else {
nativeAppendOrInsertBefore(renderer, parentRNode, childRNode, anchorNode, false);
}
}
_processI18nInsertBefore !== undefined &&
_processI18nInsertBefore(renderer, childTNode, lView, childRNode, parentRNode);
}
/**
* Returns the first native node for a given LView, starting from the provided TNode.
*
* Native nodes are returned in the order in which those appear in the native tree (DOM).
*/
function getFirstNativeNode(lView, tNode) {
if (tNode !== null) {
ngDevMode &&
assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */ | 32 /* TNodeType.Icu */ | 16 /* TNodeType.Projection */);
const tNodeType = tNode.type;
if (tNodeType & 3 /* TNodeType.AnyRNode */) {
return getNativeByTNode(tNode, lView);
}
else if (tNodeType & 4 /* TNodeType.Container */) {
return getBeforeNodeForView(-1, lView[tNode.index]);
}
else if (tNodeType & 8 /* TNodeType.ElementContainer */) {
const elIcuContainerChild = tNode.child;
if (elIcuContainerChild !== null) {
return getFirstNativeNode(lView, elIcuContainerChild);
}
else {
const rNodeOrLContainer = lView[tNode.index];
if (isLContainer(rNodeOrLContainer)) {
return getBeforeNodeForView(-1, rNodeOrLContainer);
}
else {
return unwrapRNode(rNodeOrLContainer);
}
}
}
else if (tNodeType & 32 /* TNodeType.Icu */) {
let nextRNode = icuContainerIterate(tNode, lView);
let rNode = nextRNode();
// If the ICU container has no nodes, than we use the ICU anchor as the node.
return rNode || unwrapRNode(lView[tNode.index]);
}
else {
const projectionNodes = getProjectionNodes(lView, tNode);
if (projectionNodes !== null) {
if (Array.isArray(projectionNodes)) {
return projectionNodes[0];
}
const parentView = getLViewParent(lView[DECLARATION_COMPONENT_VIEW]);
ngDevMode && assertParentView(parentView);
return getFirstNativeNode(parentView, projectionNodes);
}
else {
return getFirstNativeNode(lView, tNode.next);
}
}
}
return null;
}
function getProjectionNodes(lView, tNode) {
if (tNode !== null) {
const componentView = lView[DECLARATION_COMPONENT_VIEW];
const componentHost = componentView[T_HOST];
const slotIdx = tNode.projection;
ngDevMode && assertProjectionSlots(lView);
return componentHost.projection[slotIdx];
}
return null;
}
function getBeforeNodeForView(viewIndexInContainer, lContainer) {
const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1;
if (nextViewIndex < lContainer.length) {
const lView = lContainer[nextViewIndex];
const firstTNodeOfView = lView[TVIEW].firstChild;
if (firstTNodeOfView !== null) {
return getFirstNativeNode(lView, firstTNodeOfView);
}
}
return lContainer[NATIVE];
}
/**
* Removes a native node itself using a given renderer. To remove the node we are looking up its
* parent from the native tree as not all platforms / browsers support the equivalent of
* node.remove().
*
* @param renderer A renderer to be used
* @param rNode The native node that should be removed
* @param isHostElement A flag indicating if a node to be removed is a host of a component.
*/
function nativeRemoveNode(renderer, rNode, isHostElement) {
ngDevMode && ngDevMode.rendererRemoveNode++;
const nativeParent = nativeParentNode(renderer, rNode);
if (nativeParent) {
nativeRemoveChild(renderer, nativeParent, rNode, isHostElement);
}
}
/**
* Clears the contents of a given RElement.
*
* @param rElement the native RElement to be cleared
*/
function clearElementContents(rElement) {
rElement.textContent = '';
}
/**
* Performs the operation of `action` on the node. Typically this involves inserting or removing
* nodes on the LView or projection boundary.
*/
function applyNodes(renderer, action, tNode, lView, parentRElement, beforeNode, isProjection) {
while (tNode != null) {
ngDevMode && assertTNodeForLView(tNode, lView);
ngDevMode &&
assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */ | 16 /* TNodeType.Projection */ | 32 /* TNodeType.Icu */);
const rawSlotValue = lView[tNode.index];
const tNodeType = tNode.type;
if (isProjection) {
if (action === 0 /* WalkTNodeTreeAction.Create */) {
rawSlotValue && attachPatchData(unwrapRNode(rawSlotValue), lView);
tNode.flags |= 2 /* TNodeFlags.isProjected */;
}
}
if ((tNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) {
if (tNodeType & 8 /* TNodeType.ElementContainer */) {
applyNodes(renderer, action, tNode.child, lView, parentRElement, beforeNode, false);
applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
}
else if (tNodeType & 32 /* TNodeType.Icu */) {
const nextRNode = icuContainerIterate(tNode, lView);
let rNode;
while (rNode = nextRNode()) {
applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode);
}
applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
}
else if (tNodeType & 16 /* TNodeType.Projection */) {
applyProjectionRecursive(renderer, action, lView, tNode, parentRElement, beforeNode);
}
else {
ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 4 /* TNodeType.Container */);
applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
}
}
tNode = isProjection ? tNode.projectionNext : tNode.next;
}
}
function applyView(tView, lView, renderer, action, parentRElement, beforeNode) {
applyNodes(renderer, action, tView.firstChild, lView, parentRElement, beforeNode, false);
}
/**
* `applyProjection` performs operation on the projection.
*
* Inserting a projection requires us to locate the projected nodes from the parent component. The
* complication is that those nodes themselves could be re-projected from their parent component.
*
* @param tView The `TView` of `LView` which needs to be inserted, detached, destroyed
* @param lView The `LView` which needs to be inserted, detached, destroyed.
* @param tProjectionNode node to project
*/
function applyProjection(tView, lView, tProjectionNode) {
const renderer = lView[RENDERER];
const parentRNode = getParentRElement(tView, tProjectionNode, lView);
const parentTNode = tProjectionNode.parent || lView[T_HOST];
let beforeNode = getInsertInFrontOfRNode(parentTNode, tProjectionNode, lView);
applyProjectionRecursive(renderer, 0 /* WalkTNodeTreeAction.Create */, lView, tProjectionNode, parentRNode, beforeNode);
}
/**
* `applyProjectionRecursive` performs operation on the projection specified by `action` (insert,
* detach, destroy)
*
* Inserting a projection requires us to locate the projected nodes from the parent component. The
* complication is that those nodes themselves could be re-projected from their parent component.
*
* @param renderer Render to use
* @param action action to perform (insert, detach, destroy)
* @param lView The LView which needs to be inserted, detached, destroyed.
* @param tProjectionNode node to project
* @param parentRElement parent DOM element for insertion/removal.
* @param beforeNode Before which node the insertions should happen.
*/
function applyProjectionRecursive(renderer, action, lView, tProjectionNode, parentRElement, beforeNode) {
const componentLView = lView[DECLARATION_COMPONENT_VIEW];
const componentNode = componentLView[T_HOST];
ngDevMode &&
assertEqual(typeof tProjectionNode.projection, 'number', 'expecting projection index');
const nodeToProjectOrRNodes = componentNode.projection[tProjectionNode.projection];
if (Array.isArray(nodeToProjectOrRNodes)) {
// This should not exist, it is a bit of a hack. When we bootstrap a top level node and we
// need to support passing projectable nodes, so we cheat and put them in the TNode
// of the Host TView. (Yes we put instance info at the T Level). We can get away with it
// because we know that that TView is not shared and therefore it will not be a problem.
// This should be refactored and cleaned up.
for (let i = 0; i < nodeToProjectOrRNodes.length; i++) {
const rNode = nodeToProjectOrRNodes[i];
applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode);
}
}
else {
let nodeToProject = nodeToProjectOrRNodes;
const projectedComponentLView = componentLView[PARENT];
// If a parent is located within a skip hydration block,
// annotate an actual node that is being projected with the same flag too.
if (hasInSkipHydrationBlockFlag(tProjectionNode)) {
nodeToProject.flags |= 128 /* TNodeFlags.inSkipHydrationBlock */;
}
applyNodes(renderer, action, nodeToProject, projectedComponentLView, parentRElement, beforeNode, true);
}
}
/**
* `applyContainer` performs an operation on the container and its views as specified by
* `action` (insert, detach, destroy)
*
* Inserting a Container is complicated by the fact that the container may have Views which
* themselves have containers or projections.
*
* @param renderer Renderer to use
* @param action action to perform (insert, detach, destroy)
* @param lContainer The LContainer which needs to be inserted, detached, destroyed.
* @param parentRElement parent DOM element for insertion/removal.
* @param beforeNode Before which node the insertions should happen.
*/
function applyContainer(renderer, action, lContainer, parentRElement, beforeNode) {
ngDevMode && assertLContainer(lContainer);
const anchor = lContainer[NATIVE]; // LContainer has its own before node.
const native = unwrapRNode(lContainer);
// An LContainer can be created dynamically on any node by injecting ViewContainerRef.
// Asking for a ViewContainerRef on an element will result in a creation of a separate anchor
// node (comment in the DOM) that will be different from the LContainer's host node. In this
// particular case we need to execute action on 2 nodes:
// - container's host node (this is done in the executeActionOnElementOrContainer)
// - container's host node (this is done here)
if (anchor !== native) {
// This is very strange to me (Misko). I would expect that the native is same as anchor. I
// don't see a reason why they should be different, but they are.
//
// If they are we need to process the second anchor as well.
applyToElementOrContainer(action, renderer, parentRElement, anchor, beforeNode);
}
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
const lView = lContainer[i];
applyView(lView[TVIEW], lView, renderer, action, parentRElement, anchor);
}
}
/**
* Writes class/style to element.
*
* @param renderer Renderer to use.
* @param isClassBased `true` if it should be written to `class` (`false` to write to `style`)
* @param rNode The Node to write to.
* @param prop Property to write to. This would be the class/style name.
* @param value Value to write. If `null`/`undefined`/`false` this is considered a remove (set/add
* otherwise).
*/
function applyStyling(renderer, isClassBased, rNode, prop, value) {
if (isClassBased) {
// We actually want JS true/false here because any truthy value should add the class
if (!value) {
ngDevMode && ngDevMode.rendererRemoveClass++;
renderer.removeClass(rNode, prop);
}
else {
ngDevMode && ngDevMode.rendererAddClass++;
renderer.addClass(rNode, prop);
}
}
else {
let flags = prop.indexOf('-') === -1 ? undefined : RendererStyleFlags2.DashCase;
if (value == null /** || value === undefined */) {
ngDevMode && ngDevMode.rendererRemoveStyle++;
renderer.removeStyle(rNode, prop, flags);
}
else {
// A value is important if it ends with `!important`. The style
// parser strips any semicolons at the end of the value.
const isImportant = typeof value === 'string' ? value.endsWith('!important') : false;
if (isImportant) {
// !important has to be stripped from the value for it to be valid.
value = value.slice(0, -10);
flags |= RendererStyleFlags2.Important;
}
ngDevMode && ngDevMode.rendererSetStyle++;
renderer.setStyle(rNode, prop, value, flags);
}
}
}
/**
* Write `cssText` to `RElement`.
*
* This function does direct write without any reconciliation. Used for writing initial values, so
* that static styling values do not pull in the style parser.
*
* @param renderer Renderer to use
* @param element The element which needs to be updated.
* @param newValue The new class list to write.
*/
function writeDirectStyle(renderer, element, newValue) {
ngDevMode && assertString(newValue, '\'newValue\' should be a string');
renderer.setAttribute(element, 'style', newValue);
ngDevMode && ngDevMode.rendererSetStyle++;
}
/**
* Write `className` to `RElement`.
*
* This function does direct write without any reconciliation. Used for writing initial values, so
* that static styling values do not pull in the style parser.
*
* @param renderer Renderer to use
* @param element The element which needs to be updated.
* @param newValue The new class list to write.
*/
function writeDirectClass(renderer, element, newValue) {
ngDevMode && assertString(newValue, '\'newValue\' should be a string');
if (newValue === '') {
// There are tests in `google3` which expect `element.getAttribute('class')` to be `null`.
renderer.removeAttribute(element, 'class');
}
else {
renderer.setAttribute(element, 'class', newValue);
}
ngDevMode && ngDevMode.rendererSetClassName++;
}
/** Sets up the static DOM attributes on an `RNode`. */
function setupStaticAttributes(renderer, element, tNode) {
const { mergedAttrs, classes, styles } = tNode;
if (mergedAttrs !== null) {
setUpAttributes(renderer, element, mergedAttrs);
}
if (classes !== null) {
writeDirectClass(renderer, element, classes);
}
if (styles !== null) {
writeDirectStyle(renderer, element, styles);
}
}
/**
* @fileoverview
* A module to facilitate use of a Trusted Types policy internally within
* Angular. It lazily constructs the Trusted Types policy, providing helper
* utilities for promoting strings to Trusted Types. When Trusted Types are not
* available, strings are used as a fallback.
* @security All use of this module is security-sensitive and should go through
* security review.
*/
/**
* The Trusted Types policy, or null if Trusted Types are not
* enabled/supported, or undefined if the policy has not been created yet.
*/
let policy$1;
/**
* Returns the Trusted Types policy, or null if Trusted Types are not
* enabled/supported. The first call to this function will create the policy.
*/
function getPolicy$1() {
if (policy$1 === undefined) {
policy$1 = null;
if (_global.trustedTypes) {
try {
policy$1 = _global.trustedTypes.createPolicy('angular', {
createHTML: (s) => s,
createScript: (s) => s,
createScriptURL: (s) => s,
});
}
catch {
// trustedTypes.createPolicy throws if called with a name that is
// already registered, even in report-only mode. Until the API changes,
// catch the error not to break the applications functionally. In such
// cases, the code will fall back to using strings.
}
}
}
return policy$1;
}
/**
* Unsafely promote a string to a TrustedHTML, falling back to strings when
* Trusted Types are not available.
* @security This is a security-sensitive function; any use of this function
* must go through security review. In particular, it must be assured that the
* provided string will never cause an XSS vulnerability if used in a context
* that will be interpreted as HTML by a browser, e.g. when assigning to
* element.innerHTML.
*/
function trustedHTMLFromString(html) {
return getPolicy$1()?.createHTML(html) || html;
}
/**
* Unsafely promote a string to a TrustedScript, falling back to strings when
* Trusted Types are not available.
* @security In particular, it must be assured that the provided string will
* never cause an XSS vulnerability if used in a context that will be
* interpreted and executed as a script by a browser, e.g. when calling eval.
*/
function trustedScriptFromString(script) {
return getPolicy$1()?.createScript(script) || script;
}
/**
* Unsafely promote a string to a TrustedScriptURL, falling back to strings
* when Trusted Types are not available.
* @security This is a security-sensitive function; any use of this function
* must go through security review. In particular, it must be assured that the
* provided string will never cause an XSS vulnerability if used in a context
* that will cause a browser to load and execute a resource, e.g. when
* assigning to script.src.
*/
function trustedScriptURLFromString(url) {
return getPolicy$1()?.createScriptURL(url) || url;
}
/**
* Unsafely call the Function constructor with the given string arguments. It
* is only available in development mode, and should be stripped out of
* production code.
* @security This is a security-sensitive function; any use of this function
* must go through security review. In particular, it must be assured that it
* is only called from development code, as use in production code can lead to
* XSS vulnerabilities.
*/
function newTrustedFunctionForDev(...args) {
if (typeof ngDevMode === 'undefined') {
throw new Error('newTrustedFunctionForDev should never be called in production');
}
if (!_global.trustedTypes) {
// In environments that don't support Trusted Types, fall back to the most
// straightforward implementation:
return new Function(...args);
}
// Chrome currently does not support passing TrustedScript to the Function
// constructor. The following implements the workaround proposed on the page
// below, where the Chromium bug is also referenced:
// https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
const fnArgs = args.slice(0, -1).join(',');
const fnBody = args[args.length - 1];
const body = `(function anonymous(${fnArgs}
) { ${fnBody}
})`;
// Using eval directly confuses the compiler and prevents this module from
// being stripped out of JS binaries even if not used. The global['eval']
// indirection fixes that.
const fn = _global['eval'](trustedScriptFromString(body));
if (fn.bind === undefined) {
// Workaround for a browser bug that only exists in Chrome 83, where passing
// a TrustedScript to eval just returns the TrustedScript back without
// evaluating it. In that case, fall back to the most straightforward
// implementation:
return new Function(...args);
}
// To completely mimic the behavior of calling "new Function", two more
// things need to happen:
// 1. Stringifying the resulting function should return its source code
fn.toString = () => body;
// 2. When calling the resulting function, `this` should refer to `global`
return fn.bind(_global);
// When Trusted Types support in Function constructors is widely available,
// the implementation of this function can be simplified to:
// return new Function(...args.map(a => trustedScriptFromString(a)));
}
/**
* Validation function invoked at runtime for each binding that might potentially
* represent a security-sensitive attribute of an