/** * @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 {Sanitizer} from '../sanitization/security'; import {assertDefined, assertEqual} from './assert'; import {executeHooks} from './hooks'; import {TElementNode, TNode, TNodeFlags, TViewNode} from './interfaces/node'; import {LQueries} from './interfaces/query'; import {Renderer3, RendererFactory3} from './interfaces/renderer'; import {BINDING_INDEX, CLEANUP, CONTEXT, DECLARATION_VIEW, FLAGS, HOST_NODE, LViewData, LViewFlags, OpaqueViewState, QUERIES, RENDERER, SANITIZER, TVIEW, TView} from './interfaces/view'; import {assertDataInRangeInternal, isContentQueryHost} from './util'; /** * This property gets set before entering a template. * * This renderer can be one of two varieties of Renderer3: * * - ObjectedOrientedRenderer3 * * This is the native browser API style, e.g. operations are methods on individual objects * like HTMLElement. With this style, no additional code is needed as a facade (reducing payload * size). * * - ProceduralRenderer3 * * In non-native browser environments (e.g. platforms such as web-workers), this is the facade * that enables element manipulation. This also facilitates backwards compatibility with * Renderer2. */ let renderer: Renderer3; export function getRenderer(): Renderer3 { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return renderer; } export function setRenderer(r: Renderer3): void { renderer = r; } let rendererFactory: RendererFactory3; export function getRendererFactory(): RendererFactory3 { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return rendererFactory; } export function setRendererFactory(factory: RendererFactory3): void { rendererFactory = factory; } export function getCurrentSanitizer(): Sanitizer|null { return viewData && viewData[SANITIZER]; } /** * Store the element depth count. This is used to identify the root elements of the template * so that we can than attach `LViewData` 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--; } /** * 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. * * *
* ``` */ 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. * * *
* ``` */ export function disableBindings(): void { bindingsEnabled = false; } /** * Returns the current OpaqueViewState instance. * * Used in conjunction with the restoreView() 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. */ export function getCurrentView(): OpaqueViewState { return viewData as any as OpaqueViewState; } /** * 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. */ export function restoreView(viewToRestore: OpaqueViewState) { contextViewData = viewToRestore as any as LViewData; } /** 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: LViewData) { previousOrParentTNode = tNode; viewData = 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; } let tView: TView; export function getTView(): TView { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return tView; } let currentQueries: LQueries|null; export function getCurrentQueries(): LQueries|null { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return currentQueries; } export function setCurrentQueries(queries: LQueries | null): void { currentQueries = queries; } /** * Query instructions can ask for "current queries" in 2 different cases: * - when creating view queries (at the root of a component view, before any node is created - in * this case currentQueries points to view queries) * - when creating content queries (i.e. this previousOrParentTNode points to a node on which we * create content queries). */ export function getOrCreateCurrentQueries( QueryType: {new (parent: null, shallow: null, deep: null): LQueries}): LQueries { // if this is the first content query on a node, any existing LQueries needs to be cloned // in subsequent template passes, the cloning occurs before directive instantiation. if (previousOrParentTNode && previousOrParentTNode !== viewData[HOST_NODE] && !isContentQueryHost(previousOrParentTNode)) { currentQueries && (currentQueries = currentQueries.clone()); previousOrParentTNode.flags |= TNodeFlags.hasContentQuery; } return currentQueries || (currentQueries = new QueryType(null, null, null)); } /** * This property gets set before entering a template. */ let creationMode: boolean; export function getCreationMode(): boolean { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return 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 viewData: LViewData; /** * Internal function that returns the current LViewData instance. * * The getCurrentView() instruction should be used for anything public. */ export function getViewData(): LViewData { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return viewData; } /** * The last viewData retrieved by nextContext(). * Allows building nextContext() and reference() calls. * * e.g. const inner = x().$implicit; const outer = x().$implicit; */ let contextViewData: LViewData = null !; export function getContextViewData(): LViewData { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return contextViewData; } export function getCleanup(view: LViewData): any[] { // top level variables should not be exported for performance reasons (PERF_NOTES.md) return view[CLEANUP] || (view[CLEANUP] = []); } export function getTViewCleanup(view: LViewData): any[] { return view[TVIEW].cleanup || (view[TVIEW].cleanup = []); } /** * 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; } /** Whether or not this is the first time the current view has been processed. */ let firstTemplatePass = true; export function getFirstTemplatePass(): boolean { return firstTemplatePass; } export function setFirstTemplatePass(value: boolean): void { firstTemplatePass = value; } /** * 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; } /** * 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: LViewData, hostTNode: TElementNode | TViewNode | null): LViewData { const oldView: LViewData = viewData; tView = newView && newView[TVIEW]; creationMode = newView && (newView[FLAGS] & LViewFlags.CreationMode) === LViewFlags.CreationMode; firstTemplatePass = newView && tView.firstTemplatePass; bindingRootIndex = newView && tView.bindingStartIndex; renderer = newView && newView[RENDERER]; previousOrParentTNode = hostTNode !; isParent = true; viewData = contextViewData = newView; oldView && (oldView[QUERIES] = currentQueries); currentQueries = newView && newView[QUERIES]; return oldView; } export function nextContextImpl(level: number = 1): T { contextViewData = walkUpViews(level, contextViewData !); return contextViewData[CONTEXT] as T; } function walkUpViews(nestingLevel: number, currentView: LViewData): LViewData { 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 * @param creationOnly An optional boolean to indicate that the view was processed in creation mode * only, i.e. the first update will be done later. Only possible for dynamically created views. */ export function leaveView(newView: LViewData, creationOnly?: boolean): void { if (!creationOnly) { if (!checkNoChangesMode) { executeHooks(viewData, tView.viewHooks, tView.viewCheckHooks, creationMode); } // Views are clean and in update mode after being checked, so these bits are cleared viewData[FLAGS] &= ~(LViewFlags.CreationMode | LViewFlags.Dirty); } viewData[FLAGS] |= LViewFlags.RunInit; viewData[BINDING_INDEX] = tView.bindingStartIndex; enterView(newView, null); } export function assertPreviousIsParent() { assertEqual(isParent, true, 'previousOrParentTNode should be a parent'); } export function assertHasParent() { assertDefined(previousOrParentTNode.parent, 'previousOrParentTNode should have a parent'); } export function assertDataInRange(index: number, arr?: any[]) { if (arr == null) arr = viewData; assertDataInRangeInternal(index, arr || viewData); } export function assertDataNext(index: number, arr?: any[]) { if (arr == null) arr = viewData; assertEqual( arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`); }