UNPKG

@angular/core

Version:

Angular - the core framework

1,241 lines 245 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import { ErrorHandler } from '../../error_handler'; import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '../../metadata/schema'; import { validateAgainstEventAttributes, validateAgainstEventProperties } from '../../sanitization/sanitization'; import { assertDataInRange, assertDefined, assertDomNode, assertEqual, assertNotEqual, assertNotSame } from '../../util/assert'; import { createNamedArrayType } from '../../util/named_array_type'; import { normalizeDebugBindingName, normalizeDebugBindingValue } from '../../util/ng_reflect'; import { assertLView, assertPreviousIsParent } from '../assert'; import { attachPatchData, getComponentViewByInstance } from '../context_discovery'; import { diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode } from '../di'; import { throwMultipleComponentError } from '../errors'; import { executeHooks, executePreOrderHooks, registerPreOrderHooks } from '../hooks'; import { ACTIVE_INDEX, CONTAINER_HEADER_OFFSET } from '../interfaces/container'; import { INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory } from '../interfaces/injector'; import { isProceduralRenderer } from '../interfaces/renderer'; import { BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TVIEW, T_HOST } from '../interfaces/view'; import { assertNodeOfPossibleTypes, assertNodeType } from '../node_assert'; import { isNodeMatchingSelectorList } from '../node_selector_matcher'; import { enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, isCreationMode, leaveView, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex, ɵɵnamespaceHTML } from '../state'; import { initializeStaticContext as initializeStaticStylingContext } from '../styling/class_and_style_bindings'; import { ANIMATION_PROP_PREFIX, isAnimationProp } from '../styling/util'; import { NO_CHANGE } from '../tokens'; import { attrsStylingIndexOf } from '../util/attrs_utils'; import { INTERPOLATION_DELIMITER, renderStringify, stringifyForError } from '../util/misc_utils'; import { getLViewParent, getRootContext } from '../util/view_traversal_utils'; import { getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector } from '../util/view_utils'; import { LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeInitialData, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData } from './lview_debug'; import { selectInternal } from './select'; const ɵ0 = /** * @return {?} */ () => Promise.resolve(null); /** * A permanent marker promise which signifies that the current CD tree is * clean. * @type {?} */ const _CLEAN_PROMISE = ((ɵ0))(); /** @enum {number} */ const BindingDirection = { Input: 0, Output: 1, }; export { BindingDirection }; /** * Refreshes the view, executing the following steps in that order: * triggers init hooks, refreshes dynamic embedded views, triggers content hooks, sets host * bindings, refreshes child components. * Note: view hooks are triggered later when leaving the view. * @param {?} lView * @return {?} */ export function refreshDescendantViews(lView) { /** @type {?} */ const tView = lView[TVIEW]; /** @type {?} */ const creationMode = isCreationMode(lView); // This needs to be set before children are processed to support recursive components tView.firstTemplatePass = false; // Resetting the bindingIndex of the current LView as the next steps may trigger change detection. lView[BINDING_INDEX] = tView.bindingStartIndex; // If this is a creation pass, we should not call lifecycle hooks or evaluate bindings. // This will be done in the update pass. if (!creationMode) { /** @type {?} */ const checkNoChangesMode = getCheckNoChangesMode(); executePreOrderHooks(lView, tView, checkNoChangesMode, undefined); refreshDynamicEmbeddedViews(lView); // Content query results must be refreshed before content hooks are called. refreshContentQueries(tView, lView); resetPreOrderHookFlags(lView); executeHooks(lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode, 1 /* AfterContentInitHooksToBeRun */, undefined); setHostBindings(tView, lView); } // 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 (creationMode && tView.staticContentQueries) { refreshContentQueries(tView, lView); } refreshChildComponents(tView.components); } /** * Sets the host bindings for the current view. * @param {?} tView * @param {?} viewData * @return {?} */ export function setHostBindings(tView, viewData) { /** @type {?} */ const selectedIndex = getSelectedIndex(); try { if (tView.expandoInstructions) { /** @type {?} */ let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex; setBindingRoot(bindingRootIndex); /** @type {?} */ let currentDirectiveIndex = -1; /** @type {?} */ let currentElementIndex = -1; for (let i = 0; i < tView.expandoInstructions.length; i++) { /** @type {?} */ const instruction = tView.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. currentElementIndex = -instruction; setActiveHostElement(currentElementIndex); // Injector block and providers are taken into account. /** @type {?} */ const providerCount = ((/** @type {?} */ (tView.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; } setBindingRoot(bindingRootIndex); } else { // If it's not a number, it's a host binding function that needs to be executed. if (instruction !== null) { viewData[BINDING_INDEX] = bindingRootIndex; /** @type {?} */ const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]); instruction(2 /* Update */, hostCtx, currentElementIndex); // Each directive gets a uniqueId value that is the same for both // create and update calls when the hostBindings function is called. The // directive uniqueId is not set anywhere--it is just incremented between // each hostBindings call and is useful for helping instruction code // uniquely determine which directive is currently active when executed. incrementActiveDirectiveId(); } currentDirectiveIndex++; } } } } finally { setActiveHostElement(selectedIndex); } } /** * Refreshes content queries for all directives in the given view. * @param {?} tView * @param {?} lView * @return {?} */ function refreshContentQueries(tView, lView) { if (tView.contentQueries != null) { setCurrentQueryIndex(0); for (let i = 0; i < tView.contentQueries.length; i++) { /** @type {?} */ const directiveDefIdx = tView.contentQueries[i]; /** @type {?} */ const directiveDef = (/** @type {?} */ (tView.data[directiveDefIdx])); ngDevMode && assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined'); (/** @type {?} */ (directiveDef.contentQueries))(2 /* Update */, lView[directiveDefIdx], directiveDefIdx); } } } /** * Refreshes child components in the current view. * @param {?} components * @return {?} */ function refreshChildComponents(components) { if (components != null) { for (let i = 0; i < components.length; i++) { componentRefresh(components[i]); } } } /** * Creates a native element from a tag name, using a renderer. * @param {?} name the tag name * @param {?=} overriddenRenderer Optional A renderer to override the default one * @return {?} the element created */ export function elementCreate(name, overriddenRenderer) { /** @type {?} */ let native; /** @type {?} */ const rendererToUse = overriddenRenderer || getLView()[RENDERER]; /** @type {?} */ const namespace = getNamespace(); if (isProceduralRenderer(rendererToUse)) { native = rendererToUse.createElement(name, namespace); } else { if (namespace === null) { native = rendererToUse.createElement(name); } else { native = rendererToUse.createElementNS(namespace, name); } } return native; } /** * @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 ? cloneToLView(tView.blueprint) : (/** @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 && 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, index); setPreviousOrParentTNode(tNode, true); return (/** @type {?} */ (tNode)); } /** * @param {?} tView * @param {?} tHostNode * @param {?} adjustedIndex * @param {?} type * @param {?} name * @param {?} attrs * @param {?} index * @return {?} */ function createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs, index) { /** @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(tParentNode, type, adjustedIndex, name, attrs); // 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 if (index === 0 || !tView.firstChild) { tView.firstChild = tNode; } // Now link ourselves into the tree. 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((/** @type {?} */ (tParentNode)), // 2 /* View */, index, null, null))); } return 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 {?} view * @param {?} numSlotsToAlloc * @return {?} */ export function allocExpando(view, numSlotsToAlloc) { /** @type {?} */ const tView = view[TVIEW]; if (tView.firstTemplatePass) { for (let i = 0; i < numSlotsToAlloc; i++) { tView.blueprint.push(null); tView.data.push(null); view.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 ////////////////////////// /** * Used for creating the LViewNode of a dynamic embedded view, * either through ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView(). * Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below). * @template T * @param {?} tView * @param {?} context * @param {?} declarationView * @param {?} queries * @param {?} injectorIndex * @return {?} */ export function createEmbeddedViewAndNode(tView, context, declarationView, queries, injectorIndex) { /** @type {?} */ const _isParent = getIsParent(); /** @type {?} */ const _previousOrParentTNode = getPreviousOrParentTNode(); setPreviousOrParentTNode((/** @type {?} */ (null)), true); /** @type {?} */ const lView = createLView(declarationView, tView, context, 16 /* CheckAlways */, null, null); lView[DECLARATION_VIEW] = declarationView; if (queries) { lView[QUERIES] = queries.createView(); } assignTViewNodeToLView(tView, null, -1, lView); if (tView.firstTemplatePass) { (/** @type {?} */ (tView.node)).injectorIndex = injectorIndex; } setPreviousOrParentTNode(_previousOrParentTNode, _isParent); return lView; } /** * Used for rendering embedded views (e.g. dynamically created views) * * Dynamically created views must store/retrieve their TViews differently from component views * because their template functions are nested in the template functions of their hosts, creating * closures. If their host template happens to be an embedded template in a loop (e.g. ngFor * inside * an ngFor), the nesting would mean we'd have multiple instances of the template function, so we * can't store TViews in the template function itself (as we do for comps). Instead, we store the * TView for dynamically created views on their host TNode, which only has one instance. * @template T * @param {?} viewToRender * @param {?} tView * @param {?} context * @return {?} */ export function renderEmbeddedTemplate(viewToRender, tView, context) { /** @type {?} */ const _isParent = getIsParent(); /** @type {?} */ const _previousOrParentTNode = getPreviousOrParentTNode(); /** @type {?} */ let oldView; if (viewToRender[FLAGS] & 512 /* IsRoot */) { // This is a root view inside the view tree tickRootContext(getRootContext(viewToRender)); } else { // Will become true if the `try` block executes with no errors. /** @type {?} */ let safeToRunHooks = false; try { setPreviousOrParentTNode((/** @type {?} */ (null)), true); oldView = enterView(viewToRender, viewToRender[T_HOST]); resetPreOrderHookFlags(viewToRender); executeTemplate(viewToRender, (/** @type {?} */ (tView.template)), getRenderFlags(viewToRender), context); // 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 firstTemplatePass. If we don't set it here, instances will perform directive // matching, etc again and again. viewToRender[TVIEW].firstTemplatePass = false; refreshDescendantViews(viewToRender); safeToRunHooks = true; } finally { leaveView((/** @type {?} */ (oldView)), safeToRunHooks); setPreviousOrParentTNode(_previousOrParentTNode, _isParent); } } } /** * @template T * @param {?} hostView * @param {?} context * @param {?=} templateFn * @return {?} */ export function renderComponentOrTemplate(hostView, context, templateFn) { /** @type {?} */ const rendererFactory = hostView[RENDERER_FACTORY]; /** @type {?} */ const oldView = enterView(hostView, hostView[T_HOST]); /** @type {?} */ const normalExecutionPath = !getCheckNoChangesMode(); /** @type {?} */ const creationModeIsActive = isCreationMode(hostView); // Will become true if the `try` block executes with no errors. /** @type {?} */ let safeToRunHooks = false; try { if (normalExecutionPath && !creationModeIsActive && rendererFactory.begin) { rendererFactory.begin(); } if (creationModeIsActive) { // creation mode pass templateFn && executeTemplate(hostView, templateFn, 1 /* Create */, context); refreshDescendantViews(hostView); hostView[FLAGS] &= ~4 /* CreationMode */; } // update mode pass resetPreOrderHookFlags(hostView); templateFn && executeTemplate(hostView, templateFn, 2 /* Update */, context); refreshDescendantViews(hostView); safeToRunHooks = true; } finally { if (normalExecutionPath && !creationModeIsActive && rendererFactory.end) { rendererFactory.end(); } leaveView(oldView, safeToRunHooks); } } /** * @template T * @param {?} lView * @param {?} templateFn * @param {?} rf * @param {?} context * @return {?} */ function executeTemplate(lView, templateFn, rf, context) { ɵɵnamespaceHTML(); /** @type {?} */ const prevSelectedIndex = getSelectedIndex(); try { setActiveHostElement(null); if (rf & 2 /* Update */) { // When we're updating, have an inherent ɵɵselect(0) so we don't have to generate that // instruction for most update blocks selectInternal(lView, 0); } templateFn(rf, context); } finally { setSelectedIndex(prevSelectedIndex); } } /** * This function returns the default configuration of rendering flags depending on when the * template is in creation mode or update mode. Update block and create block are * always run separately. * @param {?} view * @return {?} */ function getRenderFlags(view) { return isCreationMode(view) ? 1 /* Create */ : 2 /* Update */; } ////////////////////////// //// Element ////////////////////////// /** * Appropriately sets `stylingTemplate` on a TNode * * Does not apply styles to DOM nodes * * @param {?} tView * @param {?} tNode The node whose `stylingTemplate` to set * @param {?} attrs The attribute array source to set the attributes from * @param {?} attrsStartIndex Optional start index to start processing the `attrs` from * @return {?} */ export function setNodeStylingTemplate(tView, tNode, attrs, attrsStartIndex) { if (tView.firstTemplatePass && !tNode.stylingTemplate) { /** @type {?} */ const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, attrsStartIndex); if (stylingAttrsStartIndex >= 0) { tNode.stylingTemplate = initializeStaticStylingContext(attrs, stylingAttrsStartIndex); } } } /** * @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 and populates local refs. * * @param {?} tView * @param {?} lView * @param {?} localRefs Local refs of the node in question * @param {?=} localRefExtractor mapping function that extracts local ref value from TNode * @return {?} */ export function createDirectivesAndLocals(tView, lView, localRefs, localRefExtractor = getNativeByTNode) { if (!getBindingsEnabled()) return; /** @type {?} */ const previousOrParentTNode = getPreviousOrParentTNode(); if (tView.firstTemplatePass) { ngDevMode && ngDevMode.firstTemplatePass++; resolveDirectives(tView, lView, findDirectiveMatches(tView, lView, previousOrParentTNode), previousOrParentTNode, localRefs || null); } instantiateAllDirectives(tView, lView, previousOrParentTNode); invokeDirectivesHostBindings(tView, lView, previousOrParentTNode); saveResolvedLocalsInData(lView, previousOrParentTNode, localRefExtractor); setActiveHostElement(null); } /** * 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 {?} */ function saveResolvedLocalsInData(viewData, tNode, localRefExtractor) { /** @type {?} */ const localNames = tNode.localNames; if (localNames) { /** @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 getOrCreateTView(def) { return def.tView || (def.tView = createTView(-1, def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery, def.schemas)); } /** * Creates a TView instance * * @param {?} viewIndex The viewBlockId for inline views, or -1 if it's a component/dynamic * @param {?} templateFn Template function * @param {?} consts 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 * @return {?} */ export function createTView(viewIndex, templateFn, consts, vars, directives, pipes, viewQuery, schemas) { ngDevMode && ngDevMode.tView++; /** @type {?} */ const bindingStartIndex = HEADER_OFFSET + consts; // 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(viewIndex, // id: number, blueprint, // blueprint: LView, templateFn, // template: ComponentTemplate<{}>|null, viewQuery, (/** @type {?} */ (null)), // node: TViewNode|TElementNode|null, cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData, bindingStartIndex, // bindingStartIndex: number, initialViewLength, // viewQueryStartIndex: number, initialViewLength, // expandoStartIndex: number, null, // expandoInstructions: ExpandoInstructions|null, true, // firstTemplatePass: 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: HookData|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) : { id: viewIndex, blueprint: blueprint, template: templateFn, viewQuery: viewQuery, node: (/** @type {?} */ (null)), data: blueprint.slice().fill(null, bindingStartIndex), bindingStartIndex: bindingStartIndex, viewQueryStartIndex: initialViewLength, expandoStartIndex: initialViewLength, expandoInstructions: null, firstTemplatePass: 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, }; } /** * @param {?} bindingStartIndex * @param {?} initialViewLength * @return {?} */ function createViewBlueprint(bindingStartIndex, initialViewLength) { /** @type {?} */ const blueprint = (/** @type {?} */ (new (ngDevMode ? (/** @type {?} */ (LViewBlueprint)) : Array)(initialViewLength) .fill(null, 0, bindingStartIndex) .fill(NO_CHANGE, bindingStartIndex))); blueprint[BINDING_INDEX] = bindingStartIndex; return blueprint; } /** * @param {?} text * @param {?} token * @return {?} */ export function createError(text, token) { return new Error(`Renderer: ${text} [${stringifyForError(token)}]`); } /** * Locates the host native element, used for bootstrapping existing nodes into rendering pipeline. * * @param {?} factory * @param {?} elementOrSelector Render element or CSS selector to locate the element. * @return {?} */ export function locateHostElement(factory, elementOrSelector) { /** @type {?} */ const defaultRenderer = factory.createRenderer(null, null); /** @type {?} */ const rNode = typeof elementOrSelector === 'string' ? (isProceduralRenderer(defaultRenderer) ? defaultRenderer.selectRootElement(elementOrSelector) : defaultRenderer.querySelector(elementOrSelector)) : elementOrSelector; if (ngDevMode && !rNode) { if (typeof elementOrSelector === 'string') { throw createError('Host node with selector not found:', elementOrSelector); } else { throw createError('Host node is required:', elementOrSelector); } } return rNode; } /** * 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 {?} lView * @param {?} context * @param {?} cleanupFn * @return {?} */ export function storeCleanupWithContext(lView, context, cleanupFn) { /** @type {?} */ const lCleanup = getCleanup(lView); lCleanup.push(context); if (lView[TVIEW].firstTemplatePass) { getTViewCleanup(lView).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 {?} view * @param {?} cleanupFn * @return {?} */ export function storeCleanupFn(view, cleanupFn) { getCleanup(view).push(cleanupFn); if (view[TVIEW].firstTemplatePass) { getTViewCleanup(view).push((/** @type {?} */ (view[CLEANUP])).length - 1, null); } } /** * Constructs a TNode object from the arguments. * * @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(tParent, type, adjustedIndex, tagName, attrs) { ngDevMode && ngDevMode.tNode++; return { type: type, index: adjustedIndex, injectorIndex: tParent ? tParent.injectorIndex : -1, directiveStart: -1, directiveEnd: -1, propertyMetadataStartIndex: -1, propertyMetadataEndIndex: -1, flags: 0, providerIndexes: 0, tagName: tagName, attrs: attrs, localNames: null, initialInputs: undefined, inputs: undefined, outputs: undefined, tViews: null, next: null, projectionNext: null, child: null, parent: tParent, stylingTemplate: null, projection: null, onElementCreationFns: null, // TODO (matsko): rename this to `styles` once the old styling impl is gone newStyles: null, // TODO (matsko): rename this to `classes` once the old styling impl is gone newClasses: null, }; } /** * Consolidates all inputs or outputs of all directives on this logical node. * * @param {?} tNode * @param {?} direction whether to consider inputs or outputs * @return {?} PropertyAliases|null aggregate of all properties if any, `null` otherwise */ export function generatePropertyAliases(tNode, direction) { /** @type {?} */ const tView = getLView()[TVIEW]; /** @type {?} */ let propStore = null; /** @type {?} */ const start = tNode.directiveStart; /** @type {?} */ const end = tNode.directiveEnd; if (end > start) { /** @type {?} */ const isInput = direction === 0 /* Input */; /** @type {?} */ const defs = tView.data; for (let i = start; i < end; i++) { /** @type {?} */ const directiveDef = (/** @type {?} */ (defs[i])); /** @type {?} */ const propertyAliasMap = isInput ? directiveDef.inputs : directiveDef.outputs; for (let publicName in propertyAliasMap) { if (propertyAliasMap.hasOwnProperty(publicName)) { propStore = propStore || {}; /** @type {?} */ const internalName = propertyAliasMap[publicName]; /** @type {?} */ const hasProperty = propStore.hasOwnProperty(publicName); hasProperty ? propStore[publicName].push(i, publicName, internalName) : (propStore[publicName] = [i, publicName, internalName]); } } } } return propStore; } /** * Mapping between attributes names that don't correspond to their element property names. * Note: this mapping has to be kept in sync with the equally named mapping in the template * type-checking machinery of ngtsc. * @type {?} */ const ATTR_TO_PROP = { 'class': 'className', 'for': 'htmlFor', 'formaction': 'formAction', 'innerHtml': 'innerHTML', 'readonly': 'readOnly', 'tabindex': 'tabIndex', }; /** * @template T * @param {?} index * @param {?} propName * @param {?} value * @param {?=} sanitizer * @param {?=} nativeOnly * @param {?=} loadRendererFn * @return {?} */ export function elementPropertyInternal(index, propName, value, sanitizer, nativeOnly, loadRendererFn) { ngDevMode && assertNotSame(value, (/** @type {?} */ (NO_CHANGE)), 'Incoming value should never be NO_CHANGE.'); /** @type {?} */ const lView = getLView(); /** @type {?} */ const element = (/** @type {?} */ (getNativeByIndex(index, lView))); /** @type {?} */ const tNode = getTNode(index, lView); /** @type {?} */ let inputData; /** @type {?} */ let dataValue; if (!nativeOnly && (inputData = initializeTNodeInputs(tNode)) && (dataValue = inputData[propName])) { setInputsForProperty(lView, dataValue, value); if (isComponent(tNode)) markDirtyIfOnPush(lView, index + HEADER_OFFSET); if (ngDevMode) { if (tNode.type === 3 /* Element */ || tNode.type === 0 /* Container */) { /** * dataValue is an array containing runtime input or output names for the directives: * i+0: directive instance index * i+1: publicName * i+2: privateName * * e.g. [0, 'change', 'change-minified'] * we want to set the reflected property with the privateName: dataValue[i+2] */ for (let i = 0; i < dataValue.length; i += 3) { setNgReflectProperty(lView, element, tNode.type, (/** @type {?} */ (dataValue[i + 2])), value); } } } } else if (tNode.type === 3 /* Element */) { propName = ATTR_TO_PROP[propName] || propName; if (ngDevMode) { validateAgainstEventProperties(propName); validateAgainstUnknownProperties(lView, element, propName, tNode); ngDevMode.rendererSetProperty++; } savePropertyDebugData(tNode, lView, propName, lView[TVIEW].data, nativeOnly); /** @type {?} */ const renderer = loadRendererFn ? loadRendererFn(tNode, lView) : lView[RENDERER]; // 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(lView, tNode.tagName)) { throw createUnknownPropertyError(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 = getComponentViewByIndex(viewIndex, lView); if (!(childComponentLView[FLAGS] & 16 /* CheckAlways */)) { childComponentLView[FLAGS] |= 64 /* Dirty */; } } /** * @param {?} lView * @param {?} element * @param {?} type * @param {?} attrName * @param {?} value * @return {?} */ export 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 {?} hostView * @param {?} element * @param {?} propName * @param {?} tNode * @return {?} */ function validateAgainstUnknownProperties(hostView, element, propName, tNode) { // If the tag matches any of the schemas we shouldn't throw. if (matchingSchemas(hostView, tNode.tagName)) { return; } // If prop is not a known property of the HTML element... if (!(propName in element) && // and we are in a browser context... (web worker nodes should be skipped) typeof Node === 'function' && element instanceof Node && // and isn't a synthetic animation property... propName[0] !== ANIMATION_PROP_PREFIX) { // ... it is probably a user error and we should throw. throw createUnknownPropertyError(propName, tNode); } } /** * @param {?} hostView * @param {?} tagName * @return {?} */ function matchingSchemas(hostView, tagName) { /** @type {?} */ const schemas = hostView[TVIEW].schemas; if (schemas !== null) { for (let i = 0; i < schemas.length; i++) { /** @type {?} */ const schema = schemas[i]; if (schema === NO_ERRORS_SCHEMA || schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) { return true; } } } return false; } /** * Stores debugging data for this property binding on first template pass. * This enables features like DebugElement.properties. * @param {?} tNode * @param {?} lView * @param {?} propName * @param {?} tData * @param {?} nativeOnly * @return {?} */ function savePropertyDebugData(tNode, lView, propName, tData, nativeOnly) { /** @type {?} */ const lastBindingIndex = lView[BINDING_INDEX] - 1; // Bind/interpolation functions save binding metadata in the last binding index, // but leave the property name blank. If the interpolation delimiter is at the 0 // index, we know that this is our first pass and the property name still needs to // be set. /** @type {?} */ const bindingMetadata = (/** @type {?} */ (tData[lastBindingIndex])); if (bindingMetadata[0] == INTERPOLATION_DELIMITER) { tData[lastBindingIndex] = propName + bindingMetadata; // We don't want to store indices for host bindings because they are stored in a // different part of LView (the expando section). if (!nativeOnly) { if (tNode.propertyMetadataStartIndex == -1) { tNode.propertyMetadataStartIndex = lastBindingIndex; } tNode.propertyMetadataEndIndex = lastBindingIndex + 1; } } } /** * Creates an error that should be thrown when encountering an unknown property on an element. * @param {?} propName Name of the invalid property. * @param {?} tNode Node on which we encountered the error. * @return {?} */ function createUnknownPropertyError(propName, tNode) { return new Error(`Template error: Can't bind to '${propName}' since it isn't a known property of '${tNode.tagName}'.`); } /** * Instantiate a root component. * @template T * @param {?} tView * @param {?} viewData * @param {?} def * @return {?} */ export function instantiateRootComponent(tView, viewData, def) { /** @type {?} */ const rootTNode = getPreviousOrParentTNode(); if (tView.firstTemplatePass) { if (def.providersResolver) def.providersResolver(def); generateExpandoInstructionBlock(tView, rootTNode, 1); baseResolveDirective(tView, viewData, def, def.factory); } /** @type {?} */ const directive = getNodeInjectable(tView.data, viewData, viewData.length - 1, (/** @type {?} */ (rootTNode))); postProcessBaseDirective(viewData, rootTNode, directive); return directive; } /** * Resolve the matched directives on a node. * @param {?} tView * @param {?} viewData * @param {?} directives * @param {?} tNode * @param {?} localRefs * @return {?} */ function resolveDirectives(tView, viewData, directives, tNode, localRefs) { // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in // tsickle. ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only'); /** @type {?} */ const exportsMap = localRefs ? { '': -1 } : null; if (directives) { initNodeFlags(tNode, tView.data.length, directives.length); // When the same token is provided by several directives on the same node, some rules apply in // the viewEngine: // - viewProviders have priority over providers // - the last directive in NgModule.declarations has priority over the previous one // So to match these rules, the order in which providers are added in the arrays is very // important. for (let i = 0; i < directives.length; i++) { /** @type {?} */ const def = (/** @type {?} */ (directives[i])); if (def.providersResolver) def.providersResolver(def); } generateExpandoInstructionBlock(tView, tNode, directives.length); /** @type {?} */ const initialPreOrderHooksLength = (tView.preOrderHooks && tView.preOrderHooks.length) || 0; /** @type {?} */ const initialPreOrderCheckHooksLength = (tView.preOrderCheckHooks && tView.preOrderCheckHooks.length) || 0; /** @type {?} */ const nodeIndex = tNode.index - HEADER_OFFSET; for (let i = 0; i < directives.length; i++) { /** @type {?} */ const def = (/** @type {?} */ (directives[i])); /** @type {?} */ const directiveDefIdx = tView.data.length; baseResolveDirective(tView, viewData, def, def.factory); saveNameToExportMap((/** @type {?} */ (tView.data)).length - 1, def, exportsMap); // Init hooks are queued now so ngOnInit is called in host components before // any projected components. registerPreOrderHooks(directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength, initialPreOrderCheckHooksLength); } } if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap); } /** * Instantiate all the directives that were previously resolved on the current node. * @param {?} tView * @param {?} lView * @param {?} tNode * @return {?} */ function instantiateAllDirectives(tView, lView, tNode) { /** @type {?} */ const start = tNode.directiveStart; /** @type {?} */ const end = tNode.directiveEnd; if (!tView.firstTemplatePass && start < end) { getOrCreateNodeInjectorForNode((/** @type {?} */ (tNode)), lView); } for (let i = start; i < end; i++) { /** @type {?} */ const def = (/** @type {?} */ (tView.data[i])); if (isComponentDef(def)) { addComponentLogic(lView, tNode, (/** @type {?} */ (def))); } /** @type {?} */ const directive = getNodeInjectable(tView.data, (/** @type {?} */ (lView)), i, (/** @type {?} */ (tNode))); postProcessDirective(lView, directive, def, i); } } /** * @param {?} tView * @param {?} viewData * @param {?} tNode * @return {?} */ function invokeDirectivesHostBindings(tView, viewData, tNode) { /** @type {?} */ const start = tNode.directiveStart; /** @type {?} */ const end = tNode.directiveEnd; /** @type {?} */ const expando = (/** @type {?} */ (tView.expandoInstructions)); /** @type {?} */ const firstTemplatePass = tView.firstTemplatePass; /** @type {?} */ const elementIndex = tNode.index - HEADER_OFFSET; /** @type {?} */ const selectedIndex = getSelectedIndex(); try { setActiveHostElement(elementIndex); for (let i = start; i < end; i++) { /** @type {?} */ const def = (/** @type {?} */ (tView.data[i])); /** @type {?} */ const directive = viewData[i]; if (def.hostBindings) { invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass); // Each directive gets a uniqueId value that is the same for both // create and update calls when the hostBindings function is called. The // directive uniqueId is not set anywhere--it is just incremented between // each hostBindings call and is useful for helping instruction code // uniquely determine which directive is currently active when executed. incrementActiveDirectiveId(); } else if (firstTemplatePass) { expando.push(null); } } } finally { setActiveHostElement(selectedIndex); } } /** * @param {?} def * @param {?} expando * @param {?} directive * @param {?} tNode * @param {?} firstTemplatePass * @return {?} */ export function invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass) { /** @type {?} */ const previousExpandoLength = expando.length; setCurrentDirectiveDef(def); /** @type {?} */ const elementIndex = tNode.index - HEADER_OFFSET; (/** @type {?} */ (def.hostBindings))(1 /* Create */, directive, elementI