/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {assertDefined, assertGreaterThan} from '../util/assert'; import {assertLViewOrUndefined} from './assert'; import {executeHooks} from './hooks'; import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {TElementNode, TNode, TViewNode} from './interfaces/node'; import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view'; import {resetPreOrderHookFlags} from './util/view_utils'; /** * Store the element depth count. This is used to identify the root elements of the template * so that we can than attach `LView` to only those elements. */ let elementDepthCount !: number; export function getElementDepthCount() { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return elementDepthCount; } export function increaseElementDepthCount() { elementDepthCount++; } export function decreaseElementDepthCount() { elementDepthCount--; } let currentDirectiveDef: DirectiveDef|ComponentDef|null = null; export function getCurrentDirectiveDef(): DirectiveDef|ComponentDef|null { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return currentDirectiveDef; } export function setCurrentDirectiveDef(def: DirectiveDef| ComponentDef| null): void { currentDirectiveDef = def; } /** * Stores whether directives should be matched to elements. * * When template contains `ngNonBindable` than we need to prevent the runtime form matching * directives on children of that element. * * Example: * ``` * * Should match component / directive. * *
* * Should not match component / directive because we are in ngNonBindable. * *
* ``` */ let bindingsEnabled !: boolean; export function getBindingsEnabled(): boolean { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return bindingsEnabled; } /** * Enables directive matching on elements. * * * Example: * ``` * * Should match component / directive. * *
* * * Should not match component / directive because we are in ngNonBindable. * * *
* ``` * * @codeGenApi */ export function ΔenableBindings(): void { bindingsEnabled = true; } /** * Disables directive matching on element. * * * Example: * ``` * * Should match component / directive. * *
* * * Should not match component / directive because we are in ngNonBindable. * * *
* ``` * * @codeGenApi */ export function ΔdisableBindings(): void { bindingsEnabled = false; } export function getLView(): LView { return lView; } /** * Used as the starting directive id value. * * All subsequent directives are incremented from this value onwards. * The reason why this value is `1` instead of `0` is because the `0` * value is reserved for the template. */ const MIN_DIRECTIVE_ID = 1; let activeDirectiveId = MIN_DIRECTIVE_ID; /** * Position depth (with respect from leaf to root) in a directive sub-class inheritance chain. */ let activeDirectiveSuperClassDepthPosition = 0; /** * Total count of how many directives are a part of an inheritance chain. * * When directives are sub-classed (extended) from one to another, Angular * needs to keep track of exactly how many were encountered so it can accurately * generate the next directive id (once the next directive id is visited). * Normally the next directive id just a single incremented value from the * previous one, however, if the previous directive is a part of an inheritance * chain (a series of sub-classed directives) then the incremented value must * also take into account the total amount of sub-classed values. * * Note that this value resets back to zero once the next directive is * visited (when `incrementActiveDirectiveId` or `setActiveHostElement` * is called). */ let activeDirectiveSuperClassHeight = 0; /** * Sets the active directive host element and resets the directive id value * (when the provided elementIndex value has changed). * * @param elementIndex the element index value for the host element where * the directive/component instance lives */ export function setActiveHostElement(elementIndex: number | null = null) { if (_selectedIndex !== elementIndex) { setSelectedIndex(elementIndex == null ? -1 : elementIndex); activeDirectiveId = MIN_DIRECTIVE_ID; activeDirectiveSuperClassDepthPosition = 0; activeDirectiveSuperClassHeight = 0; } } /** * Returns the current id value of the current directive. * * For example we have an element that has two directives on it: *
* * dirOne->hostBindings() (id == 1) * dirTwo->hostBindings() (id == 2) * * Note that this is only active when `hostBinding` functions are being processed. * * Note that directive id values are specific to an element (this means that * the same id value could be present on another element with a completely * different set of directives). */ export function getActiveDirectiveId() { return activeDirectiveId; } /** * Increments the current directive id value. * * For example we have an element that has two directives on it: *
* * dirOne->hostBindings() (index = 1) * // increment * dirTwo->hostBindings() (index = 2) * * Depending on whether or not a previous directive had any inherited * directives present, that value will be incremented in addition * to the id jumping up by one. * * Note that this is only active when `hostBinding` functions are being processed. * * Note that directive id values are specific to an element (this means that * the same id value could be present on another element with a completely * different set of directives). */ export function incrementActiveDirectiveId() { activeDirectiveId += 1 + activeDirectiveSuperClassHeight; // because we are dealing with a new directive this // means we have exited out of the inheritance chain activeDirectiveSuperClassDepthPosition = 0; activeDirectiveSuperClassHeight = 0; } /** * Set the current super class (reverse inheritance) position depth for a directive. * * For example we have two directives: Child and Other (but Child is a sub-class of Parent) *
* * // increment * parentInstance->hostBindings() (depth = 1) * // decrement * childInstance->hostBindings() (depth = 0) * otherInstance->hostBindings() (depth = 0 b/c it's a different directive) * * Note that this is only active when `hostBinding` functions are being processed. */ export function adjustActiveDirectiveSuperClassDepthPosition(delta: number) { activeDirectiveSuperClassDepthPosition += delta; // we keep track of the height value so that when the next directive is visited // then Angular knows to generate a new directive id value which has taken into // account how many sub-class directives were a part of the previous directive. activeDirectiveSuperClassHeight = Math.max(activeDirectiveSuperClassHeight, activeDirectiveSuperClassDepthPosition); } /** * Returns the current super class (reverse inheritance) depth for a directive. * * This is designed to help instruction code distinguish different hostBindings * calls from each other when a directive has extended from another directive. * Normally using the directive id value is enough, but with the case * of parent/sub-class directive inheritance more information is required. * * Note that this is only active when `hostBinding` functions are being processed. */ export function getActiveDirectiveSuperClassDepth() { return activeDirectiveSuperClassDepthPosition; } /** * 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. * * @codeGenApi */ export function ΔrestoreView(viewToRestore: OpaqueViewState) { contextLView = viewToRestore as any as LView; } /** Used to set the parent property when nodes are created and track query results. */ let previousOrParentTNode: TNode; export function getPreviousOrParentTNode(): TNode { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return previousOrParentTNode; } export function setPreviousOrParentTNode(tNode: TNode) { previousOrParentTNode = tNode; } export function setTNodeAndViewData(tNode: TNode, view: LView) { ngDevMode && assertLViewOrUndefined(view); previousOrParentTNode = tNode; lView = view; } /** * If `isParent` is: * - `true`: then `previousOrParentTNode` points to a parent node. * - `false`: then `previousOrParentTNode` points to previous node (sibling). */ let isParent: boolean; export function getIsParent(): boolean { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return isParent; } export function setIsParent(value: boolean): void { isParent = value; } /** Checks whether a given view is in creation mode */ export function isCreationMode(view: LView = lView): boolean { return (view[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode; } /** * State of the current view being processed. * * An array of nodes (text, element, container, etc), pipes, their bindings, and * any local variables that need to be stored between invocations. */ let lView: LView; /** * The last viewData retrieved by nextContext(). * Allows building nextContext() and reference() calls. * * e.g. const inner = x().$implicit; const outer = x().$implicit; */ let contextLView: LView = null !; export function getContextLView(): LView { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return contextLView; } /** * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error. * * Necessary to support ChangeDetectorRef.checkNoChanges(). */ let checkNoChangesMode = false; export function getCheckNoChangesMode(): boolean { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return checkNoChangesMode; } export function setCheckNoChangesMode(mode: boolean): void { checkNoChangesMode = mode; } /** * The root index from which pure function instructions should calculate their binding * indices. In component views, this is TView.bindingStartIndex. In a host binding * context, this is the TView.expandoStartIndex + any dirs/hostVars before the given dir. */ let bindingRootIndex: number = -1; // top level variables should not be exported for performance reasons (PERF_NOTES.md) export function getBindingRoot() { return bindingRootIndex; } export function setBindingRoot(value: number) { bindingRootIndex = value; } /** * Current index of a View or Content Query which needs to be processed next. * We iterate over the list of Queries and increment current query index at every step. */ let currentQueryIndex: number = 0; export function getCurrentQueryIndex(): number { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return currentQueryIndex; } export function setCurrentQueryIndex(value: number): void { currentQueryIndex = value; } /** * Swap the current state with a new state. * * For performance reasons we store the state 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 state for later, and when the view is * exited the state has to be restored * * @param newView New state to become active * @param host Element to which the View is a child of * @returns the previous state; */ export function enterView(newView: LView, hostTNode: TElementNode | TViewNode | null): LView { ngDevMode && assertLViewOrUndefined(newView); const oldView = lView; if (newView) { const tView = newView[TVIEW]; bindingRootIndex = tView.bindingStartIndex; } previousOrParentTNode = hostTNode !; isParent = true; lView = contextLView = newView; return oldView; } export function nextContextImpl(level: number = 1): T { contextLView = walkUpViews(level, contextLView !); return contextLView[CONTEXT] as T; } function walkUpViews(nestingLevel: number, currentView: LView): LView { 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; } /** * Resets the application state. */ export function resetComponentState() { isParent = false; previousOrParentTNode = null !; elementDepthCount = 0; bindingsEnabled = true; } /** * Used in lieu of enterView to make it clear when we are exiting a child view. This makes * the direction of traversal (up or down the view tree) a bit clearer. * * @param newView New state to become active */ export function leaveView(newView: LView): void { const tView = lView[TVIEW]; if (isCreationMode(lView)) { lView[FLAGS] &= ~LViewFlags.CreationMode; } else { try { resetPreOrderHookFlags(lView); executeHooks( lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode, InitPhaseState.AfterViewInitHooksToBeRun, undefined); } finally { // Views are clean and in update mode after being checked, so these bits are cleared lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass); lView[BINDING_INDEX] = tView.bindingStartIndex; } } enterView(newView, null); } let _selectedIndex = -1; /** * Gets 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. */ export function getSelectedIndex() { ngDevMode && assertGreaterThan( _selectedIndex, -1, 'select() should be called prior to retrieving the selected index'); return _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. */ export function setSelectedIndex(index: number) { _selectedIndex = index; } let _currentNamespace: string|null = null; /** * Sets the namespace used to create elements to `'http://www.w3.org/2000/svg'` in global state. * * @codeGenApi */ export function ΔnamespaceSVG() { _currentNamespace = 'http://www.w3.org/2000/svg'; } /** * Sets the namespace used to create elements to `'http://www.w3.org/1998/MathML/'` in global state. * * @codeGenApi */ export function ΔnamespaceMathML() { _currentNamespace = 'http://www.w3.org/1998/MathML/'; } /** * Sets the namespace used to create elements no `null`, which forces element creation to use * `createElement` rather than `createElementNS`. * * @codeGenApi */ export function ΔnamespaceHTML() { _currentNamespace = null; } export function getNamespace(): string|null { return _currentNamespace; }