|
|
|
@ -7,19 +7,23 @@
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import {ViewEncapsulation} from '../metadata/view';
|
|
|
|
|
import {assertDefined, assertDomNode} from '../util/assert';
|
|
|
|
|
|
|
|
|
|
import {assertLContainer, assertLView} from './assert';
|
|
|
|
|
import {attachPatchData} from './context_discovery';
|
|
|
|
|
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
|
|
|
|
|
import {ComponentDef} from './interfaces/definition';
|
|
|
|
|
import {NodeInjectorFactory} from './interfaces/injector';
|
|
|
|
|
import {TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
|
|
|
|
|
import {TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
|
|
|
|
|
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
|
|
|
|
|
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
|
|
|
|
import {CHILD_HEAD, CLEANUP, FLAGS, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
|
|
|
|
import {assertNodeType} from './node_assert';
|
|
|
|
|
import {StylingContext} from './interfaces/styling';
|
|
|
|
|
import {CHILD_HEAD, CLEANUP, FLAGS, HOST, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
|
|
|
|
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
|
|
|
|
import {renderStringify} from './util/misc_utils';
|
|
|
|
|
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
|
|
|
|
|
import {getNativeByTNode, isComponent, isLContainer, isLView, isRootView, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
|
|
|
|
|
import {getNativeByTNode, isLContainer, isLView, isRootView, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
|
|
|
|
|
|
|
|
|
@ -59,176 +63,40 @@ const enum WalkTNodeTreeAction {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stack used to keep track of projection nodes in walkTNodeTree.
|
|
|
|
|
*
|
|
|
|
|
* This is deliberately created outside of walkTNodeTree to avoid allocating
|
|
|
|
|
* a new array each time the function is called. Instead the array will be
|
|
|
|
|
* re-used by each invocation. This works because the function is not reentrant.
|
|
|
|
|
*/
|
|
|
|
|
const projectionNodeStack: (LView | TNode)[] = [];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Walks a tree of TNodes, applying a transformation on the element nodes, either only on the first
|
|
|
|
|
* one found, or on all of them.
|
|
|
|
|
*
|
|
|
|
|
* @param viewToWalk the view to walk
|
|
|
|
|
* @param action identifies the action to be performed on the elements
|
|
|
|
|
* @param renderer the current renderer.
|
|
|
|
|
* @param renderParent Optional the render parent node to be set in all LContainers found,
|
|
|
|
|
* required for action modes Insert and Destroy.
|
|
|
|
|
* @param beforeNode Optional the node before which elements should be added, required for action
|
|
|
|
|
* Insert.
|
|
|
|
|
*/
|
|
|
|
|
function walkTNodeTree(
|
|
|
|
|
viewToWalk: LView, action: WalkTNodeTreeAction, renderer: Renderer3,
|
|
|
|
|
renderParent: RElement | null, beforeNode?: RNode | null): void {
|
|
|
|
|
const rootTNode = viewToWalk[TVIEW].node as TViewNode;
|
|
|
|
|
let projectionNodeIndex = -1;
|
|
|
|
|
let currentView = viewToWalk;
|
|
|
|
|
let tNode: TNode|null = rootTNode.child as TNode;
|
|
|
|
|
while (tNode) {
|
|
|
|
|
let nextTNode: TNode|null = null;
|
|
|
|
|
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
|
|
|
|
|
executeNodeAction(
|
|
|
|
|
action, renderer, renderParent, getNativeByTNode(tNode, currentView), tNode, beforeNode);
|
|
|
|
|
const nodeOrContainer = currentView[tNode.index];
|
|
|
|
|
if (isLContainer(nodeOrContainer)) {
|
|
|
|
|
// This element has an LContainer, and its comment needs to be handled
|
|
|
|
|
executeNodeAction(
|
|
|
|
|
action, renderer, renderParent, nodeOrContainer[NATIVE], tNode, beforeNode);
|
|
|
|
|
const firstView = nodeOrContainer[CONTAINER_HEADER_OFFSET];
|
|
|
|
|
if (firstView) {
|
|
|
|
|
currentView = firstView;
|
|
|
|
|
nextTNode = currentView[TVIEW].node;
|
|
|
|
|
|
|
|
|
|
// When the walker enters a container, then the beforeNode has to become the local native
|
|
|
|
|
// comment node.
|
|
|
|
|
beforeNode = nodeOrContainer[NATIVE];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (tNode.type === TNodeType.Container) {
|
|
|
|
|
const lContainer = currentView ![tNode.index] as LContainer;
|
|
|
|
|
executeNodeAction(action, renderer, renderParent, lContainer[NATIVE], tNode, beforeNode);
|
|
|
|
|
const firstView = lContainer[CONTAINER_HEADER_OFFSET];
|
|
|
|
|
if (firstView) {
|
|
|
|
|
currentView = firstView;
|
|
|
|
|
nextTNode = currentView[TVIEW].node;
|
|
|
|
|
|
|
|
|
|
// When the walker enters a container, then the beforeNode has to become the local native
|
|
|
|
|
// comment node.
|
|
|
|
|
beforeNode = lContainer[NATIVE];
|
|
|
|
|
}
|
|
|
|
|
} else if (tNode.type === TNodeType.Projection) {
|
|
|
|
|
const componentView = findComponentView(currentView !);
|
|
|
|
|
const componentHost = componentView[T_HOST] as TElementNode;
|
|
|
|
|
const head: TNode|null =
|
|
|
|
|
(componentHost.projection as(TNode | null)[])[tNode.projection as number];
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(head)) {
|
|
|
|
|
for (let nativeNode of head) {
|
|
|
|
|
executeNodeAction(action, renderer, renderParent, nativeNode, tNode, beforeNode);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Must store both the TNode and the view because this projection node could be nested
|
|
|
|
|
// deeply inside embedded views, and we need to get back down to this particular nested
|
|
|
|
|
// view.
|
|
|
|
|
projectionNodeStack[++projectionNodeIndex] = tNode;
|
|
|
|
|
projectionNodeStack[++projectionNodeIndex] = currentView !;
|
|
|
|
|
if (head) {
|
|
|
|
|
currentView = componentView[PARENT] !as LView;
|
|
|
|
|
nextTNode = currentView[TVIEW].data[head.index] as TNode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, this is a View
|
|
|
|
|
nextTNode = tNode.child;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nextTNode === null) {
|
|
|
|
|
// this last node was projected, we need to get back down to its projection node
|
|
|
|
|
if (tNode.projectionNext === null && (tNode.flags & TNodeFlags.isProjected)) {
|
|
|
|
|
currentView = projectionNodeStack[projectionNodeIndex--] as LView;
|
|
|
|
|
tNode = projectionNodeStack[projectionNodeIndex--] as TNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tNode.flags & TNodeFlags.isProjected) {
|
|
|
|
|
nextTNode = tNode.projectionNext;
|
|
|
|
|
} else if (tNode.type === TNodeType.ElementContainer) {
|
|
|
|
|
nextTNode = tNode.child || tNode.next;
|
|
|
|
|
} else {
|
|
|
|
|
nextTNode = tNode.next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find the next node in the TNode tree, taking into account the place where a node is
|
|
|
|
|
* projected (in the shadow DOM) rather than where it comes from (in the light DOM).
|
|
|
|
|
*
|
|
|
|
|
* If there is no sibling node, then it goes to the next sibling of the parent node...
|
|
|
|
|
* until it reaches rootNode (at which point null is returned).
|
|
|
|
|
*/
|
|
|
|
|
while (!nextTNode) {
|
|
|
|
|
// If parent is null, we're crossing the view boundary, so we should get the host TNode.
|
|
|
|
|
tNode = tNode.parent || currentView[T_HOST];
|
|
|
|
|
|
|
|
|
|
if (tNode === null || tNode === rootTNode) return;
|
|
|
|
|
|
|
|
|
|
// When exiting a container, the beforeNode must be restored to the previous value
|
|
|
|
|
if (tNode.type === TNodeType.Container) {
|
|
|
|
|
currentView = getLViewParent(currentView) !;
|
|
|
|
|
beforeNode = currentView[tNode.index][NATIVE];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tNode.type === TNodeType.View) {
|
|
|
|
|
/**
|
|
|
|
|
* If current lView doesn't have next pointer, we try to find it by going up parents
|
|
|
|
|
* chain until:
|
|
|
|
|
* - we find an lView with a next pointer
|
|
|
|
|
* - or find a tNode with a parent that has a next pointer
|
|
|
|
|
* - or find a lContainer
|
|
|
|
|
* - or reach root TNode (in which case we exit, since we traversed all nodes)
|
|
|
|
|
*/
|
|
|
|
|
while (!currentView[NEXT] && currentView[PARENT] &&
|
|
|
|
|
!(tNode.parent && tNode.parent.next)) {
|
|
|
|
|
if (tNode === rootTNode) return;
|
|
|
|
|
currentView = currentView[PARENT] as LView;
|
|
|
|
|
if (isLContainer(currentView)) {
|
|
|
|
|
tNode = currentView[T_HOST] !;
|
|
|
|
|
currentView = currentView[PARENT];
|
|
|
|
|
beforeNode = currentView[tNode.index][NATIVE];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
tNode = currentView[T_HOST] !;
|
|
|
|
|
}
|
|
|
|
|
if (currentView[NEXT]) {
|
|
|
|
|
currentView = currentView[NEXT] as LView;
|
|
|
|
|
nextTNode = currentView[T_HOST];
|
|
|
|
|
} else {
|
|
|
|
|
nextTNode = tNode.type === TNodeType.ElementContainer && tNode.child || tNode.next;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
nextTNode = tNode.next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tNode = nextTNode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
|
|
|
|
|
* being passed as an argument.
|
|
|
|
|
*/
|
|
|
|
|
function executeNodeAction(
|
|
|
|
|
action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null, node: RNode,
|
|
|
|
|
tNode: TNode, beforeNode?: RNode | null) {
|
|
|
|
|
action: WalkTNodeTreeAction, renderer: Renderer3, parent: RElement | null,
|
|
|
|
|
lNodeToHandle: RNode | LContainer | LView | StylingContext, beforeNode?: RNode | null) {
|
|
|
|
|
ngDevMode && assertDefined(lNodeToHandle, '\'lNodeToHandle\' is undefined');
|
|
|
|
|
let lContainer: LContainer|undefined;
|
|
|
|
|
let isComponent = false;
|
|
|
|
|
// We are expecting an RNode, but in the case of a component or LContainer the `RNode` is wrapped
|
|
|
|
|
// in an array which needs to be unwrapped. We need to know if it is a component and if
|
|
|
|
|
// it has LContainer so that we can process all of those cases appropriately.
|
|
|
|
|
if (isLContainer(lNodeToHandle)) {
|
|
|
|
|
lContainer = lNodeToHandle;
|
|
|
|
|
} else if (isLView(lNodeToHandle)) {
|
|
|
|
|
isComponent = true;
|
|
|
|
|
ngDevMode && assertDefined(lNodeToHandle[HOST], 'HOST must be defined for a component LView');
|
|
|
|
|
lNodeToHandle = lNodeToHandle[HOST] !;
|
|
|
|
|
}
|
|
|
|
|
const rNode: RNode = unwrapRNode(lNodeToHandle);
|
|
|
|
|
ngDevMode && assertDomNode(rNode);
|
|
|
|
|
|
|
|
|
|
if (action === WalkTNodeTreeAction.Insert) {
|
|
|
|
|
nativeInsertBefore(renderer, parent !, node, beforeNode || null);
|
|
|
|
|
nativeInsertBefore(renderer, parent !, rNode, beforeNode || null);
|
|
|
|
|
} else if (action === WalkTNodeTreeAction.Detach) {
|
|
|
|
|
nativeRemoveNode(renderer, node, isComponent(tNode));
|
|
|
|
|
nativeRemoveNode(renderer, rNode, isComponent);
|
|
|
|
|
} else if (action === WalkTNodeTreeAction.Destroy) {
|
|
|
|
|
ngDevMode && ngDevMode.rendererDestroyNode++;
|
|
|
|
|
(renderer as ProceduralRenderer3).destroyNode !(node);
|
|
|
|
|
(renderer as ProceduralRenderer3).destroyNode !(rNode);
|
|
|
|
|
}
|
|
|
|
|
if (lContainer != null) {
|
|
|
|
|
applyContainer(renderer, action, lContainer, parent, beforeNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -244,22 +112,21 @@ export function createTextNode(value: any, renderer: Renderer3): RText {
|
|
|
|
|
* to propagate deeply into the nested containers to remove all elements in the
|
|
|
|
|
* views beneath it.
|
|
|
|
|
*
|
|
|
|
|
* @param viewToWalk The view from which elements should be added or removed
|
|
|
|
|
* @param lView The view from which elements should be added or removed
|
|
|
|
|
* @param insertMode Whether or not elements should be added (if false, removing)
|
|
|
|
|
* @param beforeNode The node before which elements should be added, if insert mode
|
|
|
|
|
*/
|
|
|
|
|
export function addRemoveViewFromContainer(
|
|
|
|
|
viewToWalk: LView, insertMode: true, beforeNode: RNode | null): void;
|
|
|
|
|
export function addRemoveViewFromContainer(viewToWalk: LView, insertMode: false): void;
|
|
|
|
|
lView: LView, insertMode: true, beforeNode: RNode | null): void;
|
|
|
|
|
export function addRemoveViewFromContainer(lView: LView, insertMode: false): void;
|
|
|
|
|
export function addRemoveViewFromContainer(
|
|
|
|
|
viewToWalk: LView, insertMode: boolean, beforeNode?: RNode | null): void {
|
|
|
|
|
const renderParent = getContainerRenderParent(viewToWalk[TVIEW].node as TViewNode, viewToWalk);
|
|
|
|
|
ngDevMode && assertNodeType(viewToWalk[TVIEW].node as TNode, TNodeType.View);
|
|
|
|
|
lView: LView, insertMode: boolean, beforeNode?: RNode | null): void {
|
|
|
|
|
const renderParent = getContainerRenderParent(lView[TVIEW].node as TViewNode, lView);
|
|
|
|
|
ngDevMode && assertNodeType(lView[TVIEW].node as TNode, TNodeType.View);
|
|
|
|
|
if (renderParent) {
|
|
|
|
|
const renderer = viewToWalk[RENDERER];
|
|
|
|
|
walkTNodeTree(
|
|
|
|
|
viewToWalk, insertMode ? WalkTNodeTreeAction.Insert : WalkTNodeTreeAction.Detach, renderer,
|
|
|
|
|
renderParent, beforeNode);
|
|
|
|
|
const renderer = lView[RENDERER];
|
|
|
|
|
const action = insertMode ? WalkTNodeTreeAction.Insert : WalkTNodeTreeAction.Detach;
|
|
|
|
|
applyView(renderer, action, lView, renderParent, beforeNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -269,7 +136,7 @@ export function addRemoveViewFromContainer(
|
|
|
|
|
* @param lView the `LView` to be detached.
|
|
|
|
|
*/
|
|
|
|
|
export function renderDetachView(lView: LView) {
|
|
|
|
|
walkTNodeTree(lView, WalkTNodeTreeAction.Detach, lView[RENDERER], null);
|
|
|
|
|
applyView(lView[RENDERER], WalkTNodeTreeAction.Detach, lView, null, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -409,16 +276,16 @@ export function removeView(lContainer: LContainer, removeIndex: number) {
|
|
|
|
|
* A standalone function which destroys an LView,
|
|
|
|
|
* conducting cleanup (e.g. removing listeners, calling onDestroys).
|
|
|
|
|
*
|
|
|
|
|
* @param view The view to be destroyed.
|
|
|
|
|
* @param lView The view to be destroyed.
|
|
|
|
|
*/
|
|
|
|
|
export function destroyLView(view: LView) {
|
|
|
|
|
if (!(view[FLAGS] & LViewFlags.Destroyed)) {
|
|
|
|
|
const renderer = view[RENDERER];
|
|
|
|
|
export function destroyLView(lView: LView) {
|
|
|
|
|
if (!(lView[FLAGS] & LViewFlags.Destroyed)) {
|
|
|
|
|
const renderer = lView[RENDERER];
|
|
|
|
|
if (isProceduralRenderer(renderer) && renderer.destroyNode) {
|
|
|
|
|
walkTNodeTree(view, WalkTNodeTreeAction.Destroy, renderer, null);
|
|
|
|
|
applyView(renderer, WalkTNodeTreeAction.Destroy, lView, null, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destroyViewTree(view);
|
|
|
|
|
destroyViewTree(lView);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -485,8 +352,8 @@ function cleanUpView(view: LView | LContainer): void {
|
|
|
|
|
|
|
|
|
|
/** Removes listeners and unsubscribes from output subscriptions */
|
|
|
|
|
function removeListeners(lView: LView): void {
|
|
|
|
|
const tCleanup = lView[TVIEW].cleanup !;
|
|
|
|
|
if (tCleanup != null) {
|
|
|
|
|
const tCleanup = lView[TVIEW].cleanup;
|
|
|
|
|
if (tCleanup !== null) {
|
|
|
|
|
const lCleanup = lView[CLEANUP] !;
|
|
|
|
|
for (let i = 0; i < tCleanup.length - 1; i += 2) {
|
|
|
|
|
if (typeof tCleanup[i] === 'string') {
|
|
|
|
@ -646,7 +513,7 @@ function nativeAppendChild(renderer: Renderer3, parent: RElement, child: RNode):
|
|
|
|
|
|
|
|
|
|
function nativeAppendOrInsertBefore(
|
|
|
|
|
renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode | null) {
|
|
|
|
|
if (beforeNode) {
|
|
|
|
|
if (beforeNode !== null) {
|
|
|
|
|
nativeInsertBefore(renderer, parent, child, beforeNode);
|
|
|
|
|
} else {
|
|
|
|
|
nativeAppendChild(renderer, parent, child);
|
|
|
|
@ -740,6 +607,7 @@ export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: L
|
|
|
|
|
const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1;
|
|
|
|
|
if (nextViewIndex < lContainer.length) {
|
|
|
|
|
const lView = lContainer[nextViewIndex] as LView;
|
|
|
|
|
ngDevMode && assertDefined(lView[T_HOST], 'Missing Host TNode');
|
|
|
|
|
const tViewNodeChild = (lView[T_HOST] as TViewNode).child;
|
|
|
|
|
return tViewNodeChild !== null ? getNativeByTNode(tViewNodeChild, lView) : lContainer[NATIVE];
|
|
|
|
|
} else {
|
|
|
|
@ -766,7 +634,7 @@ export function nativeRemoveNode(renderer: Renderer3, rNode: RNode, isHostElemen
|
|
|
|
|
/**
|
|
|
|
|
* Appends nodes to a target projection place. Nodes to insert were previously re-distribution and
|
|
|
|
|
* stored on a component host level.
|
|
|
|
|
* @param lView A LView where nodes are inserted (target VLview)
|
|
|
|
|
* @param lView A LView where nodes are inserted (target LView)
|
|
|
|
|
* @param tProjectionNode A projection node where previously re-distribution should be appended
|
|
|
|
|
* (target insertion place)
|
|
|
|
|
* @param selectorIndex A bucket from where nodes to project should be taken
|
|
|
|
@ -863,3 +731,174 @@ function appendProjectedNode(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* `applyView` performs operation on the view as specified in `action` (insert, detach, destroy)
|
|
|
|
|
*
|
|
|
|
|
* Inserting a view without projection or containers at top level is simple. Just iterate over the
|
|
|
|
|
* root nodes of the View, and for each node perform the `action`.
|
|
|
|
|
*
|
|
|
|
|
* Things get more complicated with containers and projections. That is because coming across:
|
|
|
|
|
* - Container: implies that we have to insert/remove/destroy the views of that container as well
|
|
|
|
|
* which in turn can have their own Containers at the View roots.
|
|
|
|
|
* - Projection: implies that we have to insert/remove/destroy the nodes of the projection. The
|
|
|
|
|
* complication is that the nodes we are projecting can themselves have Containers
|
|
|
|
|
* or other Projections.
|
|
|
|
|
*
|
|
|
|
|
* As you can see this is a very recursive problem. While the recursive implementation is not the
|
|
|
|
|
* most efficient one, trying to unroll recursion results in very complex code that is very hard (to
|
|
|
|
|
* maintain). We are sacrificing a bit of performance for readability using recursive
|
|
|
|
|
* implementation.
|
|
|
|
|
*
|
|
|
|
|
* @param renderer Renderer to use
|
|
|
|
|
* @param action action to perform (insert, detach, destroy)
|
|
|
|
|
* @param lView The LView which needs to be inserted, detached, destroyed.
|
|
|
|
|
* @param renderParent parent DOM element for insertion/removal.
|
|
|
|
|
* @param beforeNode Before which node the insertions should happen.
|
|
|
|
|
*/
|
|
|
|
|
function applyView(
|
|
|
|
|
renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView, renderParent: RElement | null,
|
|
|
|
|
beforeNode: RNode | null | undefined) {
|
|
|
|
|
const tView = lView[TVIEW];
|
|
|
|
|
ngDevMode && assertNodeType(tView.node !, TNodeType.View);
|
|
|
|
|
let viewRootTNode: TNode|null = tView.node !.child;
|
|
|
|
|
while (viewRootTNode !== null) {
|
|
|
|
|
const viewRootTNodeType = viewRootTNode.type;
|
|
|
|
|
if (viewRootTNodeType === TNodeType.ElementContainer) {
|
|
|
|
|
applyElementContainer(
|
|
|
|
|
renderer, action, lView, viewRootTNode as TElementContainerNode, renderParent,
|
|
|
|
|
beforeNode);
|
|
|
|
|
} else if (viewRootTNodeType === TNodeType.Projection) {
|
|
|
|
|
applyProjection(
|
|
|
|
|
renderer, action, lView, viewRootTNode as TProjectionNode, renderParent, beforeNode);
|
|
|
|
|
} else {
|
|
|
|
|
ngDevMode && assertNodeOfPossibleTypes(viewRootTNode, TNodeType.Element, TNodeType.Container);
|
|
|
|
|
executeNodeAction(action, renderer, renderParent, lView[viewRootTNode.index], beforeNode);
|
|
|
|
|
}
|
|
|
|
|
viewRootTNode = viewRootTNode.next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* `applyProjection` performs operation on the projection specified by `action` (insert, detach,
|
|
|
|
|
* destroy)
|
|
|
|
|
*
|
|
|
|
|
* Inserting a projection requires us to locate the projected nodes from the parent component. The
|
|
|
|
|
* complication is that those nodes themselves could be re-projected from its parent component.
|
|
|
|
|
*
|
|
|
|
|
* @param renderer Renderer to use
|
|
|
|
|
* @param action action to perform (insert, detach, destroy)
|
|
|
|
|
* @param lView The LView which needs to be inserted, detached, destroyed.
|
|
|
|
|
* @param renderParent parent DOM element for insertion/removal.
|
|
|
|
|
* @param beforeNode Before which node the insertions should happen.
|
|
|
|
|
*/
|
|
|
|
|
function applyProjection(
|
|
|
|
|
renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView,
|
|
|
|
|
tProjectionNode: TProjectionNode, renderParent: RElement | null,
|
|
|
|
|
beforeNode: RNode | null | undefined) {
|
|
|
|
|
const componentLView = findComponentView(lView);
|
|
|
|
|
const componentNode = componentLView[T_HOST] as TElementNode;
|
|
|
|
|
const nodeToProject = componentNode.projection ![tProjectionNode.projection] !;
|
|
|
|
|
if (Array.isArray(nodeToProject)) {
|
|
|
|
|
for (let i = 0; i < nodeToProject.length; i++) {
|
|
|
|
|
const rNode = nodeToProject[i];
|
|
|
|
|
ngDevMode && assertDomNode(rNode);
|
|
|
|
|
executeNodeAction(action, renderer, renderParent, rNode, beforeNode);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let projectionTNode: TNode|null = nodeToProject;
|
|
|
|
|
const projectedComponentLView = componentLView[PARENT] as LView;
|
|
|
|
|
while (projectionTNode !== null) {
|
|
|
|
|
if (projectionTNode.type === TNodeType.ElementContainer) {
|
|
|
|
|
applyElementContainer(
|
|
|
|
|
renderer, action, projectedComponentLView, projectionTNode as TElementContainerNode,
|
|
|
|
|
renderParent, beforeNode);
|
|
|
|
|
} else if (projectionTNode.type === TNodeType.Projection) {
|
|
|
|
|
applyProjection(
|
|
|
|
|
renderer, action, projectedComponentLView, projectionTNode as TProjectionNode,
|
|
|
|
|
renderParent, beforeNode);
|
|
|
|
|
} else {
|
|
|
|
|
const rNode = projectedComponentLView[projectionTNode.index];
|
|
|
|
|
ngDevMode &&
|
|
|
|
|
assertNodeOfPossibleTypes(projectionTNode, TNodeType.Element, TNodeType.Container);
|
|
|
|
|
executeNodeAction(action, renderer, renderParent, rNode, beforeNode);
|
|
|
|
|
}
|
|
|
|
|
projectionTNode = projectionTNode.projectionNext;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* `applyContainer` performs operation on the container and its views as specified by `action`
|
|
|
|
|
* (insert, detach, destroy)
|
|
|
|
|
*
|
|
|
|
|
* Inserting a Container is complicated by the fact that the container may have Views which
|
|
|
|
|
* themselves have containers or projections.
|
|
|
|
|
*
|
|
|
|
|
* @param renderer Renderer to use
|
|
|
|
|
* @param action action to perform (insert, detach, destroy)
|
|
|
|
|
* @param lContainer The LContainer which needs to be inserted, detached, destroyed.
|
|
|
|
|
* @param renderParent parent DOM element for insertion/removal.
|
|
|
|
|
* @param beforeNode Before which node the insertions should happen.
|
|
|
|
|
*/
|
|
|
|
|
function applyContainer(
|
|
|
|
|
renderer: Renderer3, action: WalkTNodeTreeAction, lContainer: LContainer,
|
|
|
|
|
renderParent: RElement | null, beforeNode: RNode | null | undefined) {
|
|
|
|
|
ngDevMode && assertLContainer(lContainer);
|
|
|
|
|
const anchor = lContainer[NATIVE]; // LContainer has its own before node.
|
|
|
|
|
const native = unwrapRNode(lContainer);
|
|
|
|
|
// A LContainer can be created dynamically on any node by injecting ViewContainerRef.
|
|
|
|
|
// Asking for a ViewContainerRef on an element will result in a creation of a separate anchor node
|
|
|
|
|
// (comment in the DOM) that will be different from the LContainer's host node. In this particular
|
|
|
|
|
// case we need to execute action on 2 nodes:
|
|
|
|
|
// - container's host node (this is done in the executeNodeAction)
|
|
|
|
|
// - container's host node (this is done here)
|
|
|
|
|
if (anchor !== native) {
|
|
|
|
|
executeNodeAction(action, renderer, renderParent, anchor, beforeNode);
|
|
|
|
|
}
|
|
|
|
|
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
|
|
|
|
|
const lView = lContainer[i] as LView;
|
|
|
|
|
applyView(renderer, action, lView, renderParent, anchor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* `applyElementContainer` performs operation on the ng-container node and its child nodes as
|
|
|
|
|
* specified by the `action` (insert, detach, destroy)
|
|
|
|
|
*
|
|
|
|
|
* @param renderer Renderer to use
|
|
|
|
|
* @param action action to perform (insert, detach, destroy)
|
|
|
|
|
* @param lView The LView which needs to be inserted, detached, destroyed.
|
|
|
|
|
* @param tElementContainerNode The TNode associated with the ElementContainer.
|
|
|
|
|
* @param renderParent parent DOM element for insertion/removal.
|
|
|
|
|
* @param beforeNode Before which node the insertions should happen.
|
|
|
|
|
*/
|
|
|
|
|
function applyElementContainer(
|
|
|
|
|
renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView,
|
|
|
|
|
tElementContainerNode: TElementContainerNode, renderParent: RElement | null,
|
|
|
|
|
beforeNode: RNode | null | undefined) {
|
|
|
|
|
const node = lView[tElementContainerNode.index];
|
|
|
|
|
executeNodeAction(action, renderer, renderParent, node, beforeNode);
|
|
|
|
|
let elementContainerRootTNode: TNode|null = tElementContainerNode.child;
|
|
|
|
|
while (elementContainerRootTNode) {
|
|
|
|
|
const elementContainerRootTNodeType = elementContainerRootTNode.type;
|
|
|
|
|
if (elementContainerRootTNodeType === TNodeType.ElementContainer) {
|
|
|
|
|
applyElementContainer(
|
|
|
|
|
renderer, action, lView, elementContainerRootTNode as TElementContainerNode, renderParent,
|
|
|
|
|
beforeNode);
|
|
|
|
|
} else if (elementContainerRootTNodeType === TNodeType.Projection) {
|
|
|
|
|
applyProjection(
|
|
|
|
|
renderer, action, lView, elementContainerRootTNode as TProjectionNode, renderParent,
|
|
|
|
|
beforeNode);
|
|
|
|
|
} else {
|
|
|
|
|
ngDevMode && assertNodeOfPossibleTypes(
|
|
|
|
|
elementContainerRootTNode, TNodeType.Element, TNodeType.Container);
|
|
|
|
|
executeNodeAction(
|
|
|
|
|
action, renderer, renderParent, lView[elementContainerRootTNode.index], beforeNode);
|
|
|
|
|
}
|
|
|
|
|
elementContainerRootTNode = elementContainerRootTNode.next;
|
|
|
|
|
}
|
|
|
|
|
}
|