@angular/core
Version:
Angular - the core framework
1,194 lines • 281 kB
JavaScript
/**
* @fileoverview added by tsickle
* Generated from: packages/core/src/render3/instructions/shared.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
import { ErrorHandler } from '../../error_handler';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '../../metadata/schema';
import { ViewEncapsulation } from '../../metadata/view';
import { validateAgainstEventAttributes, validateAgainstEventProperties } from '../../sanitization/sanitization';
import { assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame, assertSame } from '../../util/assert';
import { createNamedArrayType } from '../../util/named_array_type';
import { initNgDevMode } from '../../util/ng_dev_mode';
import { normalizeDebugBindingName, normalizeDebugBindingValue } from '../../util/ng_reflect';
import { assertFirstCreatePass, assertLContainer, assertLView } from '../assert';
import { attachPatchData } from '../context_discovery';
import { getFactoryDef } from '../definition';
import { diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode } from '../di';
import { throwMultipleComponentError } from '../errors';
import { executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags } from '../hooks';
import { CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, MOVED_VIEWS } from '../interfaces/container';
import { INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory } from '../interfaces/injector';
import { isProceduralRenderer } from '../interfaces/renderer';
import { isComponentDef, isComponentHost, isContentQueryHost, isRootView } from '../interfaces/type_checks';
import { CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, NEXT, PARENT, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TRANSPLANTED_VIEWS_TO_REFRESH, TVIEW } from '../interfaces/view';
import { assertNodeOfPossibleTypes } from '../node_assert';
import { isInlineTemplate, isNodeMatchingSelectorList } from '../node_selector_matcher';
import { enterView, getBindingsEnabled, getCheckNoChangesMode, getCurrentDirectiveIndex, getIsParent, getPreviousOrParentTNode, getSelectedIndex, leaveView, setBindingIndex, setBindingRootForHostBindings, setCheckNoChangesMode, setCurrentDirectiveIndex, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex } from '../state';
import { NO_CHANGE } from '../tokens';
import { isAnimationProp, mergeHostAttrs } from '../util/attrs_utils';
import { INTERPOLATION_DELIMITER, renderStringify, stringifyForError } from '../util/misc_utils';
import { getFirstLContainer, getLViewParent, getNextLContainer } from '../util/view_traversal_utils';
import { getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapLView, updateTransplantedViewCount, viewAttachedToChangeDetector } from '../util/view_utils';
import { selectIndexInternal } from './advance';
import { attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData, LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor } from './lview_debug';
const ɵ0 = /**
* @return {?}
*/
() => Promise.resolve(null);
/**
* A permanent marker promise which signifies that the current CD tree is
* clean.
* @type {?}
*/
const _CLEAN_PROMISE = ((ɵ0))();
/**
* Process the `TView.expandoInstructions`. (Execute the `hostBindings`.)
*
* @param {?} tView `TView` containing the `expandoInstructions`
* @param {?} lView `LView` associated with the `TView`
* @return {?}
*/
export function setHostBindingsByExecutingExpandoInstructions(tView, lView) {
ngDevMode && assertSame(tView, lView[TVIEW], '`LView` is not associated with the `TView`!');
try {
/** @type {?} */
const expandoInstructions = tView.expandoInstructions;
if (expandoInstructions !== null) {
/** @type {?} */
let bindingRootIndex = tView.expandoStartIndex;
/** @type {?} */
let currentDirectiveIndex = -1;
/** @type {?} */
let currentElementIndex = -1;
// TODO(misko): PERF It is possible to get here with `TView.expandoInstructions` containing no
// functions to execute. This is wasteful as there is no work to be done, but we still need
// to iterate over the instructions.
// In example of this is in this test: `host_binding_spec.ts`
// `fit('should not cause problems if detectChanges is called when a property updates', ...`
// In the above test we get here with expando [0, 0, 1] which requires a lot of processing but
// there is no function to execute.
for (let i = 0; i < expandoInstructions.length; i++) {
/** @type {?} */
const instruction = expandoInstructions[i];
if (typeof instruction === 'number') {
if (instruction <= 0) {
// Negative numbers mean that we are starting new EXPANDO block and need to update
// the current element and directive index.
// Important: In JS `-x` and `0-x` is not the same! If `x===0` then `-x` will produce
// `-0` which requires non standard math arithmetic and it can prevent VM optimizations.
// `0-0` will always produce `0` and will not cause a potential deoptimization in VM.
// TODO(misko): PERF This should be refactored to use `~instruction` as that does not
// suffer from `-0` and it is faster/more compact.
currentElementIndex = 0 - instruction;
setSelectedIndex(currentElementIndex);
// Injector block and providers are taken into account.
/** @type {?} */
const providerCount = ((/** @type {?} */ (expandoInstructions[++i])));
bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount;
currentDirectiveIndex = bindingRootIndex;
}
else {
// This is either the injector size (so the binding root can skip over directives
// and get to the first set of host bindings on this node) or the host var count
// (to get to the next set of host bindings on this node).
bindingRootIndex += instruction;
}
}
else {
// If it's not a number, it's a host binding function that needs to be executed.
if (instruction !== null) {
setBindingRootForHostBindings(bindingRootIndex, currentDirectiveIndex);
/** @type {?} */
const hostCtx = lView[currentDirectiveIndex];
instruction(2 /* Update */, hostCtx);
}
// TODO(misko): PERF Relying on incrementing the `currentDirectiveIndex` here is
// sub-optimal. The implications are that if we have a lot of directives but none of them
// have host bindings we nevertheless need to iterate over the expando instructions to
// update the counter. It would be much better if we could encode the
// `currentDirectiveIndex` into the `expandoInstruction` array so that we only need to
// iterate over those directives which actually have `hostBindings`.
currentDirectiveIndex++;
}
}
}
}
finally {
setSelectedIndex(-1);
}
}
/**
* Refreshes all content queries declared by directives in a given view
* @param {?} tView
* @param {?} lView
* @return {?}
*/
function refreshContentQueries(tView, lView) {
/** @type {?} */
const contentQueries = tView.contentQueries;
if (contentQueries !== null) {
for (let i = 0; i < contentQueries.length; i += 2) {
/** @type {?} */
const queryStartIdx = contentQueries[i];
/** @type {?} */
const directiveDefIdx = contentQueries[i + 1];
if (directiveDefIdx !== -1) {
/** @type {?} */
const directiveDef = (/** @type {?} */ (tView.data[directiveDefIdx]));
ngDevMode &&
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
setCurrentQueryIndex(queryStartIdx);
(/** @type {?} */ (directiveDef.contentQueries))(2 /* Update */, lView[directiveDefIdx], directiveDefIdx);
}
}
}
}
/**
* Refreshes child components in the current view (update mode).
* @param {?} hostLView
* @param {?} components
* @return {?}
*/
function refreshChildComponents(hostLView, components) {
for (let i = 0; i < components.length; i++) {
refreshComponent(hostLView, components[i]);
}
}
/**
* Renders child components in the current view (creation mode).
* @param {?} hostLView
* @param {?} components
* @return {?}
*/
function renderChildComponents(hostLView, components) {
for (let i = 0; i < components.length; i++) {
renderComponent(hostLView, components[i]);
}
}
/**
* Creates a native element from a tag name, using a renderer.
* @param {?} name the tag name
* @param {?} renderer A renderer to use
* @param {?} namespace
* @return {?} the element created
*/
export function elementCreate(name, renderer, namespace) {
if (isProceduralRenderer(renderer)) {
return renderer.createElement(name, namespace);
}
else {
return namespace === null ? renderer.createElement(name) :
renderer.createElementNS(namespace, name);
}
}
/**
* @template T
* @param {?} parentLView
* @param {?} tView
* @param {?} context
* @param {?} flags
* @param {?} host
* @param {?} tHostNode
* @param {?=} rendererFactory
* @param {?=} renderer
* @param {?=} sanitizer
* @param {?=} injector
* @return {?}
*/
export function createLView(parentLView, tView, context, flags, host, tHostNode, rendererFactory, renderer, sanitizer, injector) {
/** @type {?} */
const lView = ngDevMode ? cloneToLViewFromTViewBlueprint(tView) : (/** @type {?} */ (tView.blueprint.slice()));
lView[HOST] = host;
lView[FLAGS] = flags | 4 /* CreationMode */ | 128 /* Attached */ | 8 /* FirstLViewPass */;
resetPreOrderHookFlags(lView);
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
lView[CONTEXT] = context;
lView[RENDERER_FACTORY] = (/** @type {?} */ ((rendererFactory || parentLView && parentLView[RENDERER_FACTORY])));
ngDevMode && assertDefined(lView[RENDERER_FACTORY], 'RendererFactory is required');
lView[RENDERER] = (/** @type {?} */ ((renderer || parentLView && parentLView[RENDERER])));
ngDevMode && assertDefined(lView[RENDERER], 'Renderer is required');
lView[SANITIZER] = sanitizer || parentLView && parentLView[SANITIZER] || (/** @type {?} */ (null));
lView[(/** @type {?} */ (INJECTOR))] = injector || parentLView && parentLView[INJECTOR] || null;
lView[T_HOST] = tHostNode;
ngDevMode &&
assertEqual(tView.type == 2 /* Embedded */ ? parentLView !== null : true, true, 'Embedded views must have parentLView');
lView[DECLARATION_COMPONENT_VIEW] =
tView.type == 2 /* Embedded */ ? (/** @type {?} */ (parentLView))[DECLARATION_COMPONENT_VIEW] : lView;
ngDevMode && attachLViewDebug(lView);
return lView;
}
/**
* @param {?} tView
* @param {?} tHostNode
* @param {?} index
* @param {?} type
* @param {?} name
* @param {?} attrs
* @return {?}
*/
export function getOrCreateTNode(tView, tHostNode, index, type, name, attrs) {
// Keep this function short, so that the VM will inline it.
/** @type {?} */
const adjustedIndex = index + HEADER_OFFSET;
/** @type {?} */
const tNode = (/** @type {?} */ (tView.data[adjustedIndex])) ||
createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs);
setPreviousOrParentTNode(tNode, true);
return (/** @type {?} */ (tNode));
}
/**
* @param {?} tView
* @param {?} tHostNode
* @param {?} adjustedIndex
* @param {?} type
* @param {?} name
* @param {?} attrs
* @return {?}
*/
function createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs) {
/** @type {?} */
const previousOrParentTNode = getPreviousOrParentTNode();
/** @type {?} */
const isParent = getIsParent();
/** @type {?} */
const parent = isParent ? previousOrParentTNode : previousOrParentTNode && previousOrParentTNode.parent;
// Parents cannot cross component boundaries because components will be used in multiple places,
// so it's only set if the view is the same.
/** @type {?} */
const parentInSameView = parent && parent !== tHostNode;
/** @type {?} */
const tParentNode = parentInSameView ? (/** @type {?} */ (parent)) : null;
/** @type {?} */
const tNode = tView.data[adjustedIndex] =
createTNode(tView, tParentNode, type, adjustedIndex, name, attrs);
// Assign a pointer to the first child node of a given view. The first node is not always the one
// at index 0, in case of i18n, index 0 can be the instruction `i18nStart` and the first node has
// the index 1 or more, so we can't just check node index.
if (tView.firstChild === null) {
tView.firstChild = tNode;
}
if (previousOrParentTNode) {
if (isParent && previousOrParentTNode.child == null &&
(tNode.parent !== null || previousOrParentTNode.type === 2 /* View */)) {
// We are in the same view, which means we are adding content node to the parent view.
previousOrParentTNode.child = tNode;
}
else if (!isParent) {
previousOrParentTNode.next = tNode;
}
}
return tNode;
}
/**
* @param {?} tView
* @param {?} tParentNode
* @param {?} index
* @param {?} lView
* @return {?}
*/
export function assignTViewNodeToLView(tView, tParentNode, index, lView) {
// View nodes are not stored in data because they can be added / removed at runtime (which
// would cause indices to change). Their TNodes are instead stored in tView.node.
/** @type {?} */
let tNode = tView.node;
if (tNode == null) {
ngDevMode && tParentNode &&
assertNodeOfPossibleTypes(tParentNode, 3 /* Element */, 0 /* Container */);
tView.node = tNode = (/** @type {?} */ (createTNode(tView, (/** @type {?} */ (tParentNode)), //
2 /* View */, index, null, null)));
}
lView[T_HOST] = (/** @type {?} */ (tNode));
}
/**
* When elements are created dynamically after a view blueprint is created (e.g. through
* i18nApply() or ComponentFactory.create), we need to adjust the blueprint for future
* template passes.
*
* @param {?} tView `TView` associated with `LView`
* @param {?} lView
* @param {?} numSlotsToAlloc The number of slots to alloc in the LView, should be >0
* @return {?}
*/
export function allocExpando(tView, lView, numSlotsToAlloc) {
ngDevMode &&
assertGreaterThan(numSlotsToAlloc, 0, 'The number of slots to alloc should be greater than 0');
if (numSlotsToAlloc > 0) {
if (tView.firstCreatePass) {
for (let i = 0; i < numSlotsToAlloc; i++) {
tView.blueprint.push(null);
tView.data.push(null);
lView.push(null);
}
// We should only increment the expando start index if there aren't already directives
// and injectors saved in the "expando" section
if (!tView.expandoInstructions) {
tView.expandoStartIndex += numSlotsToAlloc;
}
else {
// Since we're adding the dynamic nodes into the expando section, we need to let the host
// bindings know that they should skip x slots
tView.expandoInstructions.push(numSlotsToAlloc);
}
}
}
}
//////////////////////////
//// Render
//////////////////////////
/**
* Processes a view in the creation mode. This includes a number of steps in a specific order:
* - creating view query functions (if any);
* - executing a template function in the creation mode;
* - updating static queries (if any);
* - creating child components defined in a given view.
* @template T
* @param {?} tView
* @param {?} lView
* @param {?} context
* @return {?}
*/
export function renderView(tView, lView, context) {
ngDevMode && assertEqual(isCreationMode(lView), true, 'Should be run in creation mode');
enterView(lView, lView[T_HOST]);
try {
/** @type {?} */
const viewQuery = tView.viewQuery;
if (viewQuery !== null) {
executeViewQueryFn(1 /* Create */, viewQuery, context);
}
// Execute a template associated with this view, if it exists. A template function might not be
// defined for the root component views.
/** @type {?} */
const templateFn = tView.template;
if (templateFn !== null) {
executeTemplate(tView, lView, templateFn, 1 /* Create */, context);
}
// This needs to be set before children are processed to support recursive components.
// This must be set to false immediately after the first creation run because in an
// ngFor loop, all the views will be created together before update mode runs and turns
// off firstCreatePass. If we don't set it here, instances will perform directive
// matching, etc again and again.
if (tView.firstCreatePass) {
tView.firstCreatePass = false;
}
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
// content queries are resolved during change detection (i.e. update mode), after embedded
// views are refreshed (see block above).
if (tView.staticContentQueries) {
refreshContentQueries(tView, lView);
}
// We must materialize query results before child components are processed
// in case a child component has projected a container. The LContainer needs
// to exist so the embedded views are properly attached by the container.
if (tView.staticViewQueries) {
executeViewQueryFn(2 /* Update */, (/** @type {?} */ (tView.viewQuery)), context);
}
// Render child component views.
/** @type {?} */
const components = tView.components;
if (components !== null) {
renderChildComponents(lView, components);
}
}
catch (error) {
// If we didn't manage to get past the first template pass due to
// an error, mark the view as corrupted so we can try to recover.
if (tView.firstCreatePass) {
tView.incompleteFirstPass = true;
}
throw error;
}
finally {
lView[FLAGS] &= ~4 /* CreationMode */;
leaveView();
}
}
/**
* Processes a view in update mode. This includes a number of steps in a specific order:
* - executing a template function in update mode;
* - executing hooks;
* - refreshing queries;
* - setting host bindings;
* - refreshing child (embedded and component) views.
* @template T
* @param {?} tView
* @param {?} lView
* @param {?} templateFn
* @param {?} context
* @return {?}
*/
export function refreshView(tView, lView, templateFn, context) {
ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode');
/** @type {?} */
const flags = lView[FLAGS];
if ((flags & 256 /* Destroyed */) === 256 /* Destroyed */)
return;
enterView(lView, lView[T_HOST]);
/** @type {?} */
const checkNoChangesMode = getCheckNoChangesMode();
try {
resetPreOrderHookFlags(lView);
setBindingIndex(tView.bindingStartIndex);
if (templateFn !== null) {
executeTemplate(tView, lView, templateFn, 2 /* Update */, context);
}
/** @type {?} */
const hooksInitPhaseCompleted = (flags & 3 /* InitPhaseStateMask */) === 3 /* InitPhaseCompleted */;
// execute pre-order hooks (OnInit, OnChanges, DoCheck)
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
if (!checkNoChangesMode) {
if (hooksInitPhaseCompleted) {
/** @type {?} */
const preOrderCheckHooks = tView.preOrderCheckHooks;
if (preOrderCheckHooks !== null) {
executeCheckHooks(lView, preOrderCheckHooks, null);
}
}
else {
/** @type {?} */
const preOrderHooks = tView.preOrderHooks;
if (preOrderHooks !== null) {
executeInitAndCheckHooks(lView, preOrderHooks, 0 /* OnInitHooksToBeRun */, null);
}
incrementInitPhaseFlags(lView, 0 /* OnInitHooksToBeRun */);
}
}
// First mark transplanted views that are declared in this lView as needing a refresh at their
// insertion points. This is needed to avoid the situation where the template is defined in this
// `LView` but its declaration appears after the insertion component.
markTransplantedViewsForRefresh(lView);
refreshEmbeddedViews(lView);
// Content query results must be refreshed before content hooks are called.
if (tView.contentQueries !== null) {
refreshContentQueries(tView, lView);
}
// execute content hooks (AfterContentInit, AfterContentChecked)
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
if (!checkNoChangesMode) {
if (hooksInitPhaseCompleted) {
/** @type {?} */
const contentCheckHooks = tView.contentCheckHooks;
if (contentCheckHooks !== null) {
executeCheckHooks(lView, contentCheckHooks);
}
}
else {
/** @type {?} */
const contentHooks = tView.contentHooks;
if (contentHooks !== null) {
executeInitAndCheckHooks(lView, contentHooks, 1 /* AfterContentInitHooksToBeRun */);
}
incrementInitPhaseFlags(lView, 1 /* AfterContentInitHooksToBeRun */);
}
}
setHostBindingsByExecutingExpandoInstructions(tView, lView);
// Refresh child component views.
/** @type {?} */
const components = tView.components;
if (components !== null) {
refreshChildComponents(lView, components);
}
// View queries must execute after refreshing child components because a template in this view
// could be inserted in a child component. If the view query executes before child component
// refresh, the template might not yet be inserted.
/** @type {?} */
const viewQuery = tView.viewQuery;
if (viewQuery !== null) {
executeViewQueryFn(2 /* Update */, viewQuery, context);
}
// execute view hooks (AfterViewInit, AfterViewChecked)
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
if (!checkNoChangesMode) {
if (hooksInitPhaseCompleted) {
/** @type {?} */
const viewCheckHooks = tView.viewCheckHooks;
if (viewCheckHooks !== null) {
executeCheckHooks(lView, viewCheckHooks);
}
}
else {
/** @type {?} */
const viewHooks = tView.viewHooks;
if (viewHooks !== null) {
executeInitAndCheckHooks(lView, viewHooks, 2 /* AfterViewInitHooksToBeRun */);
}
incrementInitPhaseFlags(lView, 2 /* AfterViewInitHooksToBeRun */);
}
}
if (tView.firstUpdatePass === true) {
// We need to make sure that we only flip the flag on successful `refreshView` only
// Don't do this in `finally` block.
// If we did this in `finally` block then an exception could block the execution of styling
// instructions which in turn would be unable to insert themselves into the styling linked
// list. The result of this would be that if the exception would not be throw on subsequent CD
// the styling would be unable to process it data and reflect to the DOM.
tView.firstUpdatePass = false;
}
// Do not reset the dirty state when running in check no changes mode. We don't want components
// to behave differently depending on whether check no changes is enabled or not. For example:
// Marking an OnPush component as dirty from within the `ngAfterViewInit` hook in order to
// refresh a `NgClass` binding should work. If we would reset the dirty state in the check
// no changes cycle, the component would be not be dirty for the next update pass. This would
// be different in production mode where the component dirty state is not reset.
if (!checkNoChangesMode) {
lView[FLAGS] &= ~(64 /* Dirty */ | 8 /* FirstLViewPass */);
}
if (lView[FLAGS] & 1024 /* RefreshTransplantedView */) {
lView[FLAGS] &= ~1024 /* RefreshTransplantedView */;
updateTransplantedViewCount((/** @type {?} */ (lView[PARENT])), -1);
}
}
finally {
leaveView();
}
}
/**
* @template T
* @param {?} tView
* @param {?} lView
* @param {?} templateFn
* @param {?} context
* @return {?}
*/
export function renderComponentOrTemplate(tView, lView, templateFn, context) {
/** @type {?} */
const rendererFactory = lView[RENDERER_FACTORY];
/** @type {?} */
const normalExecutionPath = !getCheckNoChangesMode();
/** @type {?} */
const creationModeIsActive = isCreationMode(lView);
try {
if (normalExecutionPath && !creationModeIsActive && rendererFactory.begin) {
rendererFactory.begin();
}
if (creationModeIsActive) {
renderView(tView, lView, context);
}
refreshView(tView, lView, templateFn, context);
}
finally {
if (normalExecutionPath && !creationModeIsActive && rendererFactory.end) {
rendererFactory.end();
}
}
}
/**
* @template T
* @param {?} tView
* @param {?} lView
* @param {?} templateFn
* @param {?} rf
* @param {?} context
* @return {?}
*/
function executeTemplate(tView, lView, templateFn, rf, context) {
/** @type {?} */
const prevSelectedIndex = getSelectedIndex();
try {
setSelectedIndex(-1);
if (rf & 2 /* Update */ && lView.length > HEADER_OFFSET) {
// When we're updating, inherently select 0 so we don't
// have to generate that instruction for most update blocks.
selectIndexInternal(tView, lView, 0, getCheckNoChangesMode());
}
templateFn(rf, context);
}
finally {
setSelectedIndex(prevSelectedIndex);
}
}
//////////////////////////
//// Element
//////////////////////////
/**
* @param {?} tView
* @param {?} tNode
* @param {?} lView
* @return {?}
*/
export function executeContentQueries(tView, tNode, lView) {
if (isContentQueryHost(tNode)) {
/** @type {?} */
const start = tNode.directiveStart;
/** @type {?} */
const end = tNode.directiveEnd;
for (let directiveIndex = start; directiveIndex < end; directiveIndex++) {
/** @type {?} */
const def = (/** @type {?} */ (tView.data[directiveIndex]));
if (def.contentQueries) {
def.contentQueries(1 /* Create */, lView[directiveIndex], directiveIndex);
}
}
}
}
/**
* Creates directive instances.
* @param {?} tView
* @param {?} lView
* @param {?} tNode
* @return {?}
*/
export function createDirectivesInstances(tView, lView, tNode) {
if (!getBindingsEnabled())
return;
instantiateAllDirectives(tView, lView, tNode, getNativeByTNode(tNode, lView));
if ((tNode.flags & 128 /* hasHostBindings */) === 128 /* hasHostBindings */) {
invokeDirectivesHostBindings(tView, lView, tNode);
}
}
/**
* Takes a list of local names and indices and pushes the resolved local variable values
* to LView in the same order as they are loaded in the template with load().
* @param {?} viewData
* @param {?} tNode
* @param {?=} localRefExtractor
* @return {?}
*/
export function saveResolvedLocalsInData(viewData, tNode, localRefExtractor = getNativeByTNode) {
/** @type {?} */
const localNames = tNode.localNames;
if (localNames !== null) {
/** @type {?} */
let localIndex = tNode.index + 1;
for (let i = 0; i < localNames.length; i += 2) {
/** @type {?} */
const index = (/** @type {?} */ (localNames[i + 1]));
/** @type {?} */
const value = index === -1 ?
localRefExtractor((/** @type {?} */ (tNode)), viewData) :
viewData[index];
viewData[localIndex++] = value;
}
}
}
/**
* Gets TView from a template function or creates a new TView
* if it doesn't already exist.
*
* @param {?} def ComponentDef
* @return {?} TView
*/
export function getOrCreateTComponentView(def) {
/** @type {?} */
const tView = def.tView;
// Create a TView if there isn't one, or recreate it if the first create pass didn't
// complete successfuly since we can't know for sure whether it's in a usable shape.
if (tView === null || tView.incompleteFirstPass) {
return def.tView = createTView(1 /* Component */, -1, def.template, def.decls, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery, def.schemas, def.consts);
}
return tView;
}
/**
* Creates a TView instance
*
* @param {?} type
* @param {?} viewIndex The viewBlockId for inline views, or -1 if it's a component/dynamic
* @param {?} templateFn Template function
* @param {?} decls The number of nodes, local refs, and pipes in this template
* @param {?} vars
* @param {?} directives Registry of directives for this view
* @param {?} pipes Registry of pipes for this view
* @param {?} viewQuery View queries for this view
* @param {?} schemas Schemas for this view
* @param {?} consts Constants for this view
* @return {?}
*/
export function createTView(type, viewIndex, templateFn, decls, vars, directives, pipes, viewQuery, schemas, consts) {
ngDevMode && ngDevMode.tView++;
/** @type {?} */
const bindingStartIndex = HEADER_OFFSET + decls;
// This length does not yet contain host bindings from child directives because at this point,
// we don't know which directives are active on this template. As soon as a directive is matched
// that has a host binding, we will update the blueprint with that def's hostVars count.
/** @type {?} */
const initialViewLength = bindingStartIndex + vars;
/** @type {?} */
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
return blueprint[(/** @type {?} */ (TVIEW))] = ngDevMode ?
new TViewConstructor(type, viewIndex, // id: number,
blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null,
null, // queries: TQueries|null
viewQuery, (/** @type {?} */ (null)), // node: TViewNode|TElementNode|null,
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // expandoStartIndex: number,
null, // expandoInstructions: ExpandoInstructions|null,
true, // firstCreatePass: boolean,
true, // firstUpdatePass: boolean,
false, // staticViewQueries: boolean,
false, // staticContentQueries: boolean,
null, // preOrderHooks: HookData|null,
null, // preOrderCheckHooks: HookData|null,
null, // contentHooks: HookData|null,
null, // contentCheckHooks: HookData|null,
null, // viewHooks: HookData|null,
null, // viewCheckHooks: HookData|null,
null, // destroyHooks: DestroyHookData|null,
null, // cleanup: any[]|null,
null, // contentQueries: number[]|null,
null, // components: number[]|null,
typeof directives === 'function' ?
directives() :
directives, // directiveRegistry: DirectiveDefList|null,
typeof pipes === 'function' ? pipes() : pipes, // pipeRegistry: PipeDefList|null,
null, // firstChild: TNode|null,
schemas, // schemas: SchemaMetadata[]|null,
consts, // consts: TConstants|null
false // incompleteFirstPass: boolean
) :
{
type: type,
id: viewIndex,
blueprint: blueprint,
template: templateFn,
queries: null,
viewQuery: viewQuery,
node: (/** @type {?} */ (null)),
data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex,
expandoStartIndex: initialViewLength,
expandoInstructions: null,
firstCreatePass: true,
firstUpdatePass: true,
staticViewQueries: false,
staticContentQueries: false,
preOrderHooks: null,
preOrderCheckHooks: null,
contentHooks: null,
contentCheckHooks: null,
viewHooks: null,
viewCheckHooks: null,
destroyHooks: null,
cleanup: null,
contentQueries: null,
components: null,
directiveRegistry: typeof directives === 'function' ? directives() : directives,
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
firstChild: null,
schemas: schemas,
consts: consts,
incompleteFirstPass: false
};
}
/**
* @param {?} bindingStartIndex
* @param {?} initialViewLength
* @return {?}
*/
function createViewBlueprint(bindingStartIndex, initialViewLength) {
/** @type {?} */
const blueprint = ngDevMode ? new LViewBlueprint() : [];
for (let i = 0; i < initialViewLength; i++) {
blueprint.push(i < bindingStartIndex ? null : NO_CHANGE);
}
return (/** @type {?} */ (blueprint));
}
/**
* @param {?} text
* @param {?} token
* @return {?}
*/
function createError(text, token) {
return new Error(`Renderer: ${text} [${stringifyForError(token)}]`);
}
/**
* @param {?} rElement
* @param {?} elementOrSelector
* @return {?}
*/
function assertHostNodeExists(rElement, elementOrSelector) {
if (!rElement) {
if (typeof elementOrSelector === 'string') {
throw createError('Host node with selector not found:', elementOrSelector);
}
else {
throw createError('Host node is required:', elementOrSelector);
}
}
}
/**
* Locates the host native element, used for bootstrapping existing nodes into rendering pipeline.
*
* @param {?} renderer
* @param {?} elementOrSelector Render element or CSS selector to locate the element.
* @param {?} encapsulation View Encapsulation defined for component that requests host element.
* @return {?}
*/
export function locateHostElement(renderer, elementOrSelector, encapsulation) {
if (isProceduralRenderer(renderer)) {
// When using native Shadow DOM, do not clear host element to allow native slot projection
/** @type {?} */
const preserveContent = encapsulation === ViewEncapsulation.ShadowDom;
return renderer.selectRootElement(elementOrSelector, preserveContent);
}
/** @type {?} */
let rElement = typeof elementOrSelector === 'string' ?
(/** @type {?} */ (renderer.querySelector(elementOrSelector))) :
elementOrSelector;
ngDevMode && assertHostNodeExists(rElement, elementOrSelector);
// Always clear host element's content when Renderer3 is in use. For procedural renderer case we
// make it depend on whether ShadowDom encapsulation is used (in which case the content should be
// preserved to allow native slot projection). ShadowDom encapsulation requires procedural
// renderer, and procedural renderer case is handled above.
rElement.textContent = '';
return rElement;
}
/**
* Saves context for this cleanup function in LView.cleanupInstances.
*
* On the first template pass, saves in TView:
* - Cleanup function
* - Index of context we just saved in LView.cleanupInstances
* @param {?} tView
* @param {?} lView
* @param {?} context
* @param {?} cleanupFn
* @return {?}
*/
export function storeCleanupWithContext(tView, lView, context, cleanupFn) {
/** @type {?} */
const lCleanup = getLCleanup(lView);
lCleanup.push(context);
if (tView.firstCreatePass) {
getTViewCleanup(tView).push(cleanupFn, lCleanup.length - 1);
}
}
/**
* Saves the cleanup function itself in LView.cleanupInstances.
*
* This is necessary for functions that are wrapped with their contexts, like in renderer2
* listeners.
*
* On the first template pass, the index of the cleanup function is saved in TView.
* @param {?} tView
* @param {?} lView
* @param {?} cleanupFn
* @return {?}
*/
export function storeCleanupFn(tView, lView, cleanupFn) {
getLCleanup(lView).push(cleanupFn);
if (tView.firstCreatePass) {
getTViewCleanup(tView).push((/** @type {?} */ (lView[CLEANUP])).length - 1, null);
}
}
/**
* Constructs a TNode object from the arguments.
*
* @param {?} tView `TView` to which this `TNode` belongs (used only in `ngDevMode`)
* @param {?} tParent
* @param {?} type The type of the node
* @param {?} adjustedIndex The index of the TNode in TView.data, adjusted for HEADER_OFFSET
* @param {?} tagName The tag name of the node
* @param {?} attrs The attributes defined on this node
* @return {?} the TNode object
*/
export function createTNode(tView, tParent, type, adjustedIndex, tagName, attrs) {
ngDevMode && ngDevMode.tNode++;
/** @type {?} */
let injectorIndex = tParent ? tParent.injectorIndex : -1;
return ngDevMode ? new TNodeDebug(tView, // tView_: TView
type, // type: TNodeType
adjustedIndex, // index: number
injectorIndex, // injectorIndex: number
-1, // directiveStart: number
-1, // directiveEnd: number
-1, // directiveStylingLast: number
null, // propertyBindings: number[]|null
0, // flags: TNodeFlags
0, // providerIndexes: TNodeProviderIndexes
tagName, // tagName: string|null
attrs, // attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null
null, // mergedAttrs
null, // localNames: (string|number)[]|null
undefined, // initialInputs: (string[]|null)[]|null|undefined
null, // inputs: PropertyAliases|null
null, // outputs: PropertyAliases|null
null, // tViews: ITView|ITView[]|null
null, // next: ITNode|null
null, // projectionNext: ITNode|null
null, // child: ITNode|null
tParent, // parent: TElementNode|TContainerNode|null
null, // projection: number|(ITNode|RNode[])[]|null
null, // styles: string|null
null, // stylesWithoutHost: string|null
undefined, // residualStyles: string|null
null, // classes: string|null
null, // classesWithoutHost: string|null
undefined, (/** @type {?} */ (0)), (/** @type {?} */ (0))) :
{
type: type,
index: adjustedIndex,
injectorIndex: injectorIndex,
directiveStart: -1,
directiveEnd: -1,
directiveStylingLast: -1,
propertyBindings: null,
flags: 0,
providerIndexes: 0,
tagName: tagName,
attrs: attrs,
mergedAttrs: null,
localNames: null,
initialInputs: undefined,
inputs: null,
outputs: null,
tViews: null,
next: null,
projectionNext: null,
child: null,
parent: tParent,
projection: null,
styles: null,
stylesWithoutHost: null,
residualStyles: undefined,
classes: null,
classesWithoutHost: null,
residualClasses: undefined,
classBindings: (/** @type {?} */ (0)),
styleBindings: (/** @type {?} */ (0)),
};
}
/**
* @param {?} inputAliasMap
* @param {?} directiveDefIdx
* @param {?} propStore
* @return {?}
*/
function generatePropertyAliases(inputAliasMap, directiveDefIdx, propStore) {
for (let publicName in inputAliasMap) {
if (inputAliasMap.hasOwnProperty(publicName)) {
propStore = propStore === null ? {} : propStore;
/** @type {?} */
const internalName = inputAliasMap[publicName];
if (propStore.hasOwnProperty(publicName)) {
propStore[publicName].push(directiveDefIdx, internalName);
}
else {
(propStore[publicName] = [directiveDefIdx, internalName]);
}
}
}
return propStore;
}
/**
* Initializes data structures required to work with directive outputs and outputs.
* Initialization is done for all directives matched on a given TNode.
* @param {?} tView
* @param {?} tNode
* @return {?}
*/
function initializeInputAndOutputAliases(tView, tNode) {
ngDevMode && assertFirstCreatePass(tView);
/** @type {?} */
const start = tNode.directiveStart;
/** @type {?} */
const end = tNode.directiveEnd;
/** @type {?} */
const defs = tView.data;
/** @type {?} */
const tNodeAttrs = tNode.attrs;
/** @type {?} */
const inputsFromAttrs = ngDevMode ? new TNodeInitialInputs() : [];
/** @type {?} */
let inputsStore = null;
/** @type {?} */
let outputsStore = null;
for (let i = start; i < end; i++) {
/** @type {?} */
const directiveDef = (/** @type {?} */ (defs[i]));
/** @type {?} */
const directiveInputs = directiveDef.inputs;
// Do not use unbound attributes as inputs to structural directives, since structural
// directive inputs can only be set using microsyntax (e.g. `<div *dir="exp">`).
// TODO(FW-1930): microsyntax expressions may also contain unbound/static attributes, which
// should be set for inline templates.
/** @type {?} */
const initialInputs = (tNodeAttrs !== null && !isInlineTemplate(tNode)) ?
generateInitialInputs(directiveInputs, tNodeAttrs) :
null;
inputsFromAttrs.push(initialInputs);
inputsStore = generatePropertyAliases(directiveInputs, i, inputsStore);
outputsStore = generatePropertyAliases(directiveDef.outputs, i, outputsStore);
}
if (inputsStore !== null) {
if (inputsStore.hasOwnProperty('class')) {
tNode.flags |= 16 /* hasClassInput */;
}
if (inputsStore.hasOwnProperty('style')) {
tNode.flags |= 32 /* hasStyleInput */;
}
}
tNode.initialInputs = inputsFromAttrs;
tNode.inputs = inputsStore;
tNode.outputs = outputsStore;
}
/**
* Mapping between attributes names that don't correspond to their element property names.
*
* Performance note: this function is written as a series of if checks (instead of, say, a property
* object lookup) for performance reasons - the series of `if` checks seems to be the fastest way of
* mapping property names. Do NOT change without benchmarking.
*
* Note: this mapping has to be kept in sync with the equally named mapping in the template
* type-checking machinery of ngtsc.
* @param {?} name
* @return {?}
*/
function mapPropName(name) {
if (name === 'class')
return 'className';
if (name === 'for')
return 'htmlFor';
if (name === 'formaction')
return 'formAction';
if (name === 'innerHtml')
return 'innerHTML';
if (name === 'readonly')
return 'readOnly';
if (name === 'tabindex')
return 'tabIndex';
return name;
}
/**
* @template T
* @param {?} tView
* @param {?} tNode
* @param {?} lView
* @param {?} propName
* @param {?} value
* @param {?} renderer
* @param {?} sanitizer
* @param {?} nativeOnly
* @return {?}
*/
export function elementPropertyInternal(tView, tNode, lView, propName, value, renderer, sanitizer, nativeOnly) {
ngDevMode && assertNotSame(value, (/** @type {?} */ (NO_CHANGE)), 'Incoming value should never be NO_CHANGE.');
/** @type {?} */
const element = (/** @type {?} */ (getNativeByTNode(tNode, lView)));
/** @type {?} */
let inputData = tNode.inputs;
/** @type {?} */
let dataValue;
if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
setInputsForProperty(tView, lView, dataValue, propName, value);
if (isComponentHost(tNode))
markDirtyIfOnPush(lView, tNode.index);
if (ngDevMode) {
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
}
}
else if (tNode.type === 3 /* Element */) {
propName = mapPropName(propName);
if (ngDevMode) {
validateAgainstEventProperties(propName);
if (!validateProperty(tView, lView, element, propName, tNode)) {
// Return here since we only log warnings for unknown properties.
warnAboutUnknownProperty(propName, tNode);
return;
}
ngDevMode.rendererSetProperty++;
}
// It is assumed that the sanitizer is only added when the compiler determines that the
// property is risky, so sanitization can be done without further checks.
value = sanitizer != null ? ((/** @type {?} */ (sanitizer(value, tNode.tagName || '', propName)))) : value;
if (isProceduralRenderer(renderer)) {
renderer.setProperty((/** @type {?} */ (element)), propName, value);
}
else if (!isAnimationProp(propName)) {
((/** @type {?} */ (element))).setProperty ? ((/** @type {?} */ (element))).setProperty(propName, value) :
((/** @type {?} */ (element)))[propName] = value;
}
}
else if (tNode.type === 0 /* Container */) {
// If the node is a container and the property didn't
// match any of the inputs or schemas we should throw.
if (ngDevMode && !matchingSchemas(tView, lView, tNode.tagName)) {
warnAboutUnknownProperty(propName, tNode);
}
}
}
/**
* If node is an OnPush component, marks its LView dirty.
* @param {?} lView
* @param {?} viewIndex
* @return {?}
*/
function markDirtyIfOnPush(lView, viewIndex) {
ngDevMode && assertLView(lView);
/** @type {?} */
const childComponentLView = getComponentLViewByIndex(viewIndex, lView);
if (!(childComponentLView[FLAGS] & 16 /* CheckAlways */)) {
childComponentLView[FLAGS] |= 64 /* Dirty */;
}
}
/**
* @param {?} lView
* @param {?} element
* @param {?} type
* @param {?} attrName
* @param {?} value
* @return {?}
*/
function setNgReflectProperty(lView, element, type, attrName, value) {
/** @type {?} */
const renderer = lView[RENDERER];
attrName = normalizeDebugBindingName(attrName);
/** @type {?} */
const debugValue = normalizeDebugBindingValue(value);
if (type === 3 /* Element */) {
if (value == null) {
isProceduralRenderer(renderer) ? renderer.removeAttribute(((/** @type {?} */ (element))), attrName) :
((/** @type {?} */ (element))).removeAttribute(attrName);
}
else {
isProceduralRenderer(renderer) ?
renderer.setAttribute(((/** @type {?} */ (element))), attrName, debugValue) :
((/** @type {?} */ (element))).setAttribute(attrName, debugValue);
}
}
else {
/** @type {?} */
const textContent = `bindings=${JSON.stringify({ [attrName]: debugValue }, null, 2)}`;
if (isProceduralRenderer(renderer)) {
renderer.setValue(((/** @type {?} */ (element))), textContent);
}
else {
((/** @type {?} */ (element))).textContent = textContent;
}
}
}
/**
* @param {?} lView
* @param {?} element
* @param {?} type
* @param {?} dataValue
* @param {?} value
* @return {?}
*/
export function setNgReflectProperties(lView, element, type, dataValue, value) {
if (type === 3 /* Element */ || type === 0 /* Container */) {
/**
* dataValue is an array containing runtime input or output names for the directives:
* i+0: directive instance index
* i+1: privateName
*
* e.g. [0, 'change', 'change-minified']
* we want to set the reflected property with the privateName: dataValue[i+1]
*/
for (let i = 0; i < dataValue.length; i += 2) {
setNgReflectProperty(lView, element, type, (/** @type {?} */ (dataValue[i + 1])), value);
}
}
}
/**
* @param {?} tView
* @param {?} lView
* @param {?} element
* @param {?} propName
* @param {?} tNode
* @return {?}
*/
function validateProperty(tView, lView, element, propName, tNode) {
// If `schemas` is set to `null`, that