refactor(ivy): use comment nodes to mark view containers (#24346)
PR Close #24346
This commit is contained in:
parent
153ba4dff3
commit
e3c54e4465
|
@ -266,7 +266,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
|
|||
| `data()` | n/a |
|
||||
| `destroy()` | ✅ |
|
||||
| `createElement()` | ✅ |
|
||||
| `createComment()` | n/a |
|
||||
| `createComment()` | ✅ |
|
||||
| `createText()` | ✅ |
|
||||
| `destroyNode()` | ✅ |
|
||||
| `appendChild()` | ✅ |
|
||||
|
|
|
@ -26,9 +26,9 @@ import {LInjector} from './interfaces/injector';
|
|||
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node';
|
||||
import {LQueries, QueryReadType} from './interfaces/query';
|
||||
import {Renderer3} from './interfaces/renderer';
|
||||
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, TVIEW, TView} from './interfaces/view';
|
||||
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {detachView, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||
import {appendChild, detachView, getParentLNode, insertView, removeView} from './node_manipulation';
|
||||
import {notImplemented, stringify} from './util';
|
||||
import {EmbeddedViewRef, ViewRef} from './view_ref';
|
||||
|
||||
|
@ -526,8 +526,7 @@ export class ReadFromInjectorFn<T> {
|
|||
* @returns The ElementRef instance to use
|
||||
*/
|
||||
export function getOrCreateElementRef(di: LInjector): viewEngine_ElementRef {
|
||||
return di.elementRef || (di.elementRef = new ElementRef(
|
||||
di.node.tNode.type === TNodeType.Container ? null : di.node.native));
|
||||
return di.elementRef || (di.elementRef = new ElementRef(di.node.native));
|
||||
}
|
||||
|
||||
export const QUERY_READ_TEMPLATE_REF = <QueryReadType<viewEngine_TemplateRef<any>>>(
|
||||
|
@ -574,8 +573,10 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer
|
|||
ngDevMode && assertNodeOfPossibleTypes(vcRefHost, TNodeType.Container, TNodeType.Element);
|
||||
const hostParent = getParentLNode(vcRefHost) !;
|
||||
const lContainer = createLContainer(hostParent, vcRefHost.view, true);
|
||||
const comment = vcRefHost.view[RENDERER].createComment(ngDevMode ? 'container' : '');
|
||||
const lContainerNode: LContainerNode = createLNodeObject(
|
||||
TNodeType.Container, vcRefHost.view, hostParent, undefined, lContainer, null);
|
||||
TNodeType.Container, vcRefHost.view, hostParent, comment, lContainer, null);
|
||||
appendChild(hostParent, comment, vcRefHost.view);
|
||||
|
||||
|
||||
if (vcRefHost.queries) {
|
||||
|
@ -648,9 +649,6 @@ class ViewContainerRef implements viewEngine_ViewContainerRef {
|
|||
(viewRef as EmbeddedViewRef<any>).attachToViewContainerRef(this);
|
||||
|
||||
insertView(this._lContainerNode, lViewNode, adjustedIdx);
|
||||
// invalidate cache of next sibling RNode (we do similar operation in the containerRefreshEnd
|
||||
// instruction)
|
||||
this._lContainerNode.native = undefined;
|
||||
|
||||
this._viewRefs.splice(adjustedIdx, 0, viewRef);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import {assertNodeType} from './node_assert';
|
|||
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {isDifferent, stringify} from './util';
|
||||
import {ViewRef} from './view_ref';
|
||||
|
||||
|
@ -308,7 +308,7 @@ export function createLViewData<T>(
|
|||
*/
|
||||
export function createLNodeObject(
|
||||
type: TNodeType, currentView: LViewData, parent: LNode | null,
|
||||
native: RText | RElement | null | undefined, state: any,
|
||||
native: RText | RElement | RComment | null, state: any,
|
||||
queries: LQueries | null): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode {
|
||||
return {
|
||||
native: native as any,
|
||||
|
@ -341,15 +341,15 @@ export function createLNode(
|
|||
index: number, type: TNodeType.View, native: null, name: null, attrs: null,
|
||||
lViewData: LViewData): LViewNode;
|
||||
export function createLNode(
|
||||
index: number, type: TNodeType.Container, native: undefined, name: string | null,
|
||||
index: number, type: TNodeType.Container, native: RComment, name: string | null,
|
||||
attrs: TAttributes | null, lContainer: LContainer): LContainerNode;
|
||||
export function createLNode(
|
||||
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
|
||||
lProjection: LProjection): LProjectionNode;
|
||||
export function createLNode(
|
||||
index: number, type: TNodeType, native: RText | RElement | null | undefined,
|
||||
name: string | null, attrs: TAttributes | null, state?: null | LViewData | LContainer |
|
||||
LProjection): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode {
|
||||
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
|
||||
attrs: TAttributes | null, state?: null | LViewData | LContainer | LProjection): LElementNode&
|
||||
LTextNode&LViewNode&LContainerNode&LProjectionNode {
|
||||
const parent = isParent ? previousOrParentNode :
|
||||
previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode;
|
||||
// Parents cannot cross component boundaries because components will be used in multiple places,
|
||||
|
@ -1571,8 +1571,10 @@ export function container(
|
|||
const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !;
|
||||
const lContainer = createLContainer(currentParent, viewData);
|
||||
|
||||
const node = createLNode(
|
||||
index, TNodeType.Container, undefined, tagName || null, attrs || null, lContainer);
|
||||
const comment = renderer.createComment(ngDevMode ? 'container' : '');
|
||||
const node =
|
||||
createLNode(index, TNodeType.Container, comment, tagName || null, attrs || null, lContainer);
|
||||
appendChild(getParentLNode(node), comment, viewData);
|
||||
|
||||
if (firstTemplatePass) {
|
||||
node.tNode.tViews =
|
||||
|
@ -1609,9 +1611,6 @@ export function containerRefreshStart(index: number): void {
|
|||
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
|
||||
isParent = true;
|
||||
(previousOrParentNode as LContainerNode).data[ACTIVE_INDEX] = 0;
|
||||
ngDevMode && assertSame(
|
||||
(previousOrParentNode as LContainerNode).native, undefined,
|
||||
`the container's native element should not have been set yet.`);
|
||||
|
||||
if (!checkNoChangesMode) {
|
||||
// We need to execute init hooks here so ngOnInit hooks are called in top level views
|
||||
|
@ -1635,7 +1634,6 @@ export function containerRefreshEnd(): void {
|
|||
}
|
||||
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container);
|
||||
const container = previousOrParentNode as LContainerNode;
|
||||
container.native = undefined;
|
||||
ngDevMode && assertNodeType(container, TNodeType.Container);
|
||||
const nextIndex = container.data[ACTIVE_INDEX] !;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {LContainer} from './container';
|
|||
import {LInjector} from './injector';
|
||||
import {LProjection} from './projection';
|
||||
import {LQueries} from './query';
|
||||
import {RElement, RText} from './renderer';
|
||||
import {RComment, RElement, RText} from './renderer';
|
||||
import {LViewData, TView} from './view';
|
||||
|
||||
|
||||
|
@ -63,7 +63,7 @@ export interface LNode {
|
|||
* - append children to their element parents in the DOM (e.g. `parent.native.appendChild(...)`)
|
||||
* - retrieve the sibling elements of text nodes whose creation / insertion has been delayed
|
||||
*/
|
||||
readonly native: RElement|RText|null|undefined;
|
||||
readonly native: RComment|RElement|RText|null;
|
||||
|
||||
/**
|
||||
* If regular LElementNode, then `data` will be null.
|
||||
|
@ -141,13 +141,13 @@ export interface LViewNode extends LNode {
|
|||
/** Abstract node container which contains other views. */
|
||||
export interface LContainerNode extends LNode {
|
||||
/*
|
||||
* Caches the reference of the first native node following this container in the same native
|
||||
* parent.
|
||||
* This is reset to undefined in containerRefreshEnd.
|
||||
* When it is undefined, it means the value has not been computed yet.
|
||||
* Otherwise, it contains the result of findBeforeNode(container, null).
|
||||
* This comment node is appended to the container's parent element to mark where
|
||||
* in the DOM the container's child views should be added.
|
||||
*
|
||||
* If the container is a root node of a view, this comment will not be appended
|
||||
* until the parent view is processed.
|
||||
*/
|
||||
native: RElement|RText|null|undefined;
|
||||
native: RComment;
|
||||
readonly data: LContainer;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ export type Renderer3 = ObjectOrientedRenderer3 | ProceduralRenderer3;
|
|||
* (reducing payload size).
|
||||
* */
|
||||
export interface ObjectOrientedRenderer3 {
|
||||
createComment(data: string): RComment;
|
||||
createElement(tagName: string): RElement;
|
||||
createElementNS(namespace: string, tagName: string): RElement;
|
||||
createTextNode(data: string): RText;
|
||||
|
@ -57,6 +58,7 @@ export function isProceduralRenderer(renderer: ProceduralRenderer3 | ObjectOrien
|
|||
*/
|
||||
export interface ProceduralRenderer3 {
|
||||
destroy(): void;
|
||||
createComment(value: string): RComment;
|
||||
createElement(name: string, namespace?: string|null): RElement;
|
||||
createText(value: string): RText;
|
||||
/**
|
||||
|
@ -144,6 +146,8 @@ export interface RDomTokenList {
|
|||
|
||||
export interface RText extends RNode { textContent: string|null; }
|
||||
|
||||
export interface RComment extends RNode {}
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
|
|
@ -10,60 +10,13 @@ import {callHooks} from './hooks';
|
|||
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
|
||||
import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeType, 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 {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
||||
import {CLEANUP, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||
import {assertNodeType} from './node_assert';
|
||||
import {stringify} from './util';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
|
||||
|
||||
/**
|
||||
* Returns the first RNode following the given LNode in the same parent DOM element.
|
||||
*
|
||||
* This is needed in order to insert the given node with insertBefore.
|
||||
*
|
||||
* @param node The node whose following DOM node must be found.
|
||||
* @param stopNode A parent node at which the lookup in the tree should be stopped, or null if the
|
||||
* lookup should not be stopped until the result is found.
|
||||
* @returns RNode before which the provided node should be inserted or null if the lookup was
|
||||
* stopped
|
||||
* or if there is no native node after the given logical node in the same native parent.
|
||||
*/
|
||||
function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElement|RText|null {
|
||||
let currentNode = node;
|
||||
while (currentNode && currentNode !== stopNode) {
|
||||
let pNextOrParent = currentNode.pNextOrParent;
|
||||
if (pNextOrParent) {
|
||||
while (pNextOrParent.tNode.type !== TNodeType.Projection) {
|
||||
const nativeNode = findFirstRNode(pNextOrParent);
|
||||
if (nativeNode) {
|
||||
return nativeNode;
|
||||
}
|
||||
pNextOrParent = pNextOrParent.pNextOrParent !;
|
||||
}
|
||||
currentNode = pNextOrParent;
|
||||
} else {
|
||||
let currentSibling = getNextLNode(currentNode);
|
||||
while (currentSibling) {
|
||||
const nativeNode = findFirstRNode(currentSibling);
|
||||
if (nativeNode) {
|
||||
return nativeNode;
|
||||
}
|
||||
currentSibling = getNextLNode(currentSibling);
|
||||
}
|
||||
const parentNode = getParentLNode(currentNode);
|
||||
currentNode = null;
|
||||
if (parentNode) {
|
||||
const parentType = parentNode.tNode.type;
|
||||
if (parentType === TNodeType.Container || parentType === TNodeType.View) {
|
||||
currentNode = parentNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Retrieves the sibling node for the given node. */
|
||||
export function getNextLNode(node: LNode): LNode|null {
|
||||
// View nodes don't have TNodes, so their next must be retrieved through their LView.
|
||||
|
@ -84,8 +37,8 @@ export function getChildLNode(node: LNode): LNode|null {
|
|||
}
|
||||
|
||||
/** Retrieves the parent LNode of a given node. */
|
||||
export function getParentLNode(node: LElementNode | LTextNode | LProjectionNode): LElementNode|
|
||||
LViewNode;
|
||||
export function getParentLNode(node: LContainerNode | LElementNode | LTextNode | LProjectionNode):
|
||||
LElementNode|LViewNode;
|
||||
export function getParentLNode(node: LViewNode): LContainerNode|null;
|
||||
export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null;
|
||||
export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null {
|
||||
|
@ -115,98 +68,47 @@ function getNextLNodeWithProjection(node: LNode): LNode|null {
|
|||
return getNextLNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next node in the LNode 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, this function goes to the next sibling of the parent node...
|
||||
* until it reaches rootNode (at which point null is returned).
|
||||
*
|
||||
* @param initialNode The node whose following node in the LNode tree must be found.
|
||||
* @param rootNode The root node at which the lookup should stop.
|
||||
* @return LNode|null The following node in the LNode tree.
|
||||
*/
|
||||
function getNextOrParentSiblingNode(initialNode: LNode, rootNode: LNode): LNode|null {
|
||||
let node: LNode|null = initialNode;
|
||||
let nextNode = getNextLNodeWithProjection(node);
|
||||
while (node && !nextNode) {
|
||||
// if node.pNextOrParent is not null here, it is not the next node
|
||||
// (because, at this point, nextNode is null, so it is the parent)
|
||||
node = node.pNextOrParent || getParentLNode(node);
|
||||
if (node === rootNode) {
|
||||
return null;
|
||||
}
|
||||
nextNode = node && getNextLNodeWithProjection(node);
|
||||
}
|
||||
return nextNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first RNode inside the given LNode.
|
||||
*
|
||||
* @param node The node whose first DOM node must be found
|
||||
* @returns RNode The first RNode of the given LNode or null if there is none.
|
||||
*/
|
||||
function findFirstRNode(rootNode: LNode): RElement|RText|null {
|
||||
return walkLNodeTree(rootNode, rootNode, WalkLNodeTreeAction.Find) || null;
|
||||
}
|
||||
|
||||
const enum WalkLNodeTreeAction {
|
||||
/** returns the first available native node */
|
||||
Find = 0,
|
||||
|
||||
/** node insert in the native environment */
|
||||
Insert = 1,
|
||||
Insert = 0,
|
||||
|
||||
/** node detach from the native environment */
|
||||
Detach = 2,
|
||||
Detach = 1,
|
||||
|
||||
/** node destruction using the renderer's API */
|
||||
Destroy = 3,
|
||||
Destroy = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks a tree of LNodes, applying a transformation on the LElement nodes, either only on the first
|
||||
* one found, or on all of them.
|
||||
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
|
||||
* being passed as an argument.
|
||||
*
|
||||
* @param startingNode the node from which the walk is started.
|
||||
* @param rootNode the root node considered.
|
||||
* @param action Identifies the action to be performed on the LElement nodes.
|
||||
* @param renderer Optional the current renderer, required for action modes 1, 2 and 3.
|
||||
* @param renderParentNode Optionnal the render parent node to be set in all LContainerNodes found,
|
||||
* required for action modes 1 and 2.
|
||||
* @param beforeNode Optionnal the node before which elements should be added, required for action
|
||||
* modes 1.
|
||||
* @param action identifies the action to be performed on the LElement nodes.
|
||||
* @param renderer the current renderer.
|
||||
* @param renderParentNode Optional the render parent node to be set in all LContainerNodes found,
|
||||
* required for action modes Insert and Destroy.
|
||||
* @param beforeNode Optional the node before which elements should be added, required for action
|
||||
* Insert.
|
||||
*/
|
||||
function walkLNodeTree(
|
||||
startingNode: LNode | null, rootNode: LNode, action: WalkLNodeTreeAction, renderer?: Renderer3,
|
||||
startingNode: LNode | null, rootNode: LNode, action: WalkLNodeTreeAction, renderer: Renderer3,
|
||||
renderParentNode?: LElementNode | null, beforeNode?: RNode | null) {
|
||||
let node: LNode|null = startingNode;
|
||||
while (node) {
|
||||
let nextNode: LNode|null = null;
|
||||
const parent = renderParentNode ? renderParentNode.native : null;
|
||||
if (node.tNode.type === TNodeType.Element) {
|
||||
// Execute the action
|
||||
if (action === WalkLNodeTreeAction.Find) {
|
||||
return node.native;
|
||||
} else if (action === WalkLNodeTreeAction.Insert) {
|
||||
const parent = renderParentNode !.native;
|
||||
isProceduralRenderer(renderer !) ?
|
||||
(renderer as ProceduralRenderer3)
|
||||
.insertBefore(parent !, node.native !, beforeNode as RNode | null) :
|
||||
parent !.insertBefore(node.native !, beforeNode as RNode | null, true);
|
||||
} else if (action === WalkLNodeTreeAction.Detach) {
|
||||
const parent = renderParentNode !.native;
|
||||
isProceduralRenderer(renderer !) ?
|
||||
(renderer as ProceduralRenderer3).removeChild(parent as RElement, node.native !) :
|
||||
parent !.removeChild(node.native !);
|
||||
} else if (action === WalkLNodeTreeAction.Destroy) {
|
||||
ngDevMode && ngDevMode.rendererDestroyNode++;
|
||||
(renderer as ProceduralRenderer3).destroyNode !(node.native !);
|
||||
executeNodeAction(action, renderer, parent, node.native !, beforeNode);
|
||||
if (node.dynamicLContainerNode) {
|
||||
executeNodeAction(
|
||||
action, renderer, parent, node.dynamicLContainerNode.native !, beforeNode);
|
||||
}
|
||||
nextNode = getNextLNode(node);
|
||||
} else if (node.tNode.type === TNodeType.Container) {
|
||||
executeNodeAction(action, renderer, parent, node.native !, beforeNode);
|
||||
const lContainerNode: LContainerNode = (node as LContainerNode);
|
||||
const childContainerData: LContainer = lContainerNode.dynamicLContainerNode ?
|
||||
lContainerNode.dynamicLContainerNode.data :
|
||||
|
@ -216,6 +118,13 @@ function walkLNodeTree(
|
|||
}
|
||||
nextNode =
|
||||
childContainerData[VIEWS].length ? getChildLNode(childContainerData[VIEWS][0]) : null;
|
||||
if (nextNode) {
|
||||
// When the walker enters a container, then the beforeNode has to become the local native
|
||||
// comment node.
|
||||
beforeNode = lContainerNode.dynamicLContainerNode ?
|
||||
lContainerNode.dynamicLContainerNode.native :
|
||||
lContainerNode.native;
|
||||
}
|
||||
} else if (node.tNode.type === TNodeType.Projection) {
|
||||
// For Projection look at the first projected node
|
||||
nextNode = (node as LProjectionNode).data.head;
|
||||
|
@ -224,7 +133,55 @@ function walkLNodeTree(
|
|||
nextNode = getChildLNode(node as LViewNode);
|
||||
}
|
||||
|
||||
node = nextNode === null ? getNextOrParentSiblingNode(node, rootNode) : nextNode;
|
||||
if (nextNode == null) {
|
||||
/**
|
||||
* Find the next node in the LNode 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).
|
||||
*/
|
||||
let currentNode: LNode|null = node;
|
||||
node = getNextLNodeWithProjection(currentNode);
|
||||
while (currentNode && !node) {
|
||||
// if node.pNextOrParent is not null here, it is not the next node
|
||||
// (because, at this point, nextNode is null, so it is the parent)
|
||||
currentNode = currentNode.pNextOrParent || getParentLNode(currentNode);
|
||||
if (currentNode === rootNode) {
|
||||
return null;
|
||||
}
|
||||
// When the walker exits a container, the beforeNode has to be restored to the previous
|
||||
// value.
|
||||
if (currentNode && !currentNode.pNextOrParent &&
|
||||
currentNode.tNode.type === TNodeType.Container) {
|
||||
beforeNode = currentNode.native;
|
||||
}
|
||||
node = currentNode && getNextLNodeWithProjection(currentNode);
|
||||
}
|
||||
} else {
|
||||
node = nextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
|
||||
* being passed as an argument.
|
||||
*/
|
||||
function executeNodeAction(
|
||||
action: WalkLNodeTreeAction, renderer: Renderer3, parent: RElement | null,
|
||||
node: RComment | RElement | RText, beforeNode?: RNode | null) {
|
||||
if (action === WalkLNodeTreeAction.Insert) {
|
||||
isProceduralRenderer(renderer !) ?
|
||||
(renderer as ProceduralRenderer3).insertBefore(parent !, node, beforeNode as RNode | null) :
|
||||
parent !.insertBefore(node, beforeNode as RNode | null, true);
|
||||
} else if (action === WalkLNodeTreeAction.Detach) {
|
||||
isProceduralRenderer(renderer !) ?
|
||||
(renderer as ProceduralRenderer3).removeChild(parent !, node) :
|
||||
parent !.removeChild(node);
|
||||
} else if (action === WalkLNodeTreeAction.Destroy) {
|
||||
ngDevMode && ngDevMode.rendererDestroyNode++;
|
||||
(renderer as ProceduralRenderer3).destroyNode !(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,15 +311,9 @@ export function insertView(
|
|||
// and we should wait until that parent processes its nodes (otherwise, we will insert this view's
|
||||
// nodes twice - once now and once when its parent inserts its views).
|
||||
if (container.data[RENDER_PARENT] !== null) {
|
||||
let beforeNode = findNextRNodeSibling(viewNode, container);
|
||||
|
||||
if (!beforeNode) {
|
||||
let containerNextNativeNode = container.native;
|
||||
if (containerNextNativeNode === undefined) {
|
||||
containerNextNativeNode = container.native = findNextRNodeSibling(container, null);
|
||||
}
|
||||
beforeNode = containerNextNativeNode;
|
||||
}
|
||||
// Find the node to insert in front of
|
||||
const beforeNode =
|
||||
index + 1 < views.length ? (getChildLNode(views[index + 1]) !).native : container.native;
|
||||
addRemoveViewFromContainer(container, viewNode, true, beforeNode);
|
||||
}
|
||||
|
||||
|
@ -581,9 +532,8 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: LVi
|
|||
export function appendProjectedNode(
|
||||
node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode,
|
||||
currentView: LViewData): void {
|
||||
if (node.tNode.type !== TNodeType.Container) {
|
||||
appendChild(currentParent, (node as LElementNode | LTextNode).native, currentView);
|
||||
} else {
|
||||
appendChild(currentParent, node.native, currentView);
|
||||
if (node.tNode.type === TNodeType.Container) {
|
||||
// The node we are adding is a Container and we are adding it to Element which
|
||||
// is not a component (no more re-projection).
|
||||
// Alternatively a container is projected at the root of a component's template
|
||||
|
@ -598,5 +548,6 @@ export function appendProjectedNode(
|
|||
}
|
||||
if (node.dynamicLContainerNode) {
|
||||
node.dynamicLContainerNode.data[RENDER_PARENT] = currentParent;
|
||||
appendChild(currentParent, node.dynamicLContainerNode.native, currentView);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -398,6 +398,9 @@
|
|||
{
|
||||
"name": "executeInitHooks"
|
||||
},
|
||||
{
|
||||
"name": "executeNodeAction"
|
||||
},
|
||||
{
|
||||
"name": "executeOnDestroys"
|
||||
},
|
||||
|
@ -419,12 +422,6 @@
|
|||
{
|
||||
"name": "findDirectiveMatches"
|
||||
},
|
||||
{
|
||||
"name": "findFirstRNode"
|
||||
},
|
||||
{
|
||||
"name": "findNextRNodeSibling"
|
||||
},
|
||||
{
|
||||
"name": "firstTemplatePass"
|
||||
},
|
||||
|
@ -455,9 +452,6 @@
|
|||
{
|
||||
"name": "getNextLNodeWithProjection"
|
||||
},
|
||||
{
|
||||
"name": "getNextOrParentSiblingNode"
|
||||
},
|
||||
{
|
||||
"name": "getOrCreateContainerRef"
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {defineComponent} from '../../src/render3/definition';
|
||||
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {ComponentFixture, createComponent, renderToHtml} from './render_util';
|
||||
import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from './render_util';
|
||||
|
||||
describe('JS control flow', () => {
|
||||
it('should work with if block', () => {
|
||||
|
@ -134,6 +134,76 @@ describe('JS control flow', () => {
|
|||
expect(renderToHtml(Template, ctx)).toEqual('<div><span>Hello</span></div>');
|
||||
});
|
||||
|
||||
it('should work with nested adjacent if blocks', () => {
|
||||
const ctx: {condition: boolean,
|
||||
condition2: boolean,
|
||||
condition3: boolean} = {condition: true, condition2: false, condition3: true};
|
||||
|
||||
/**
|
||||
* % if(ctx.condition) {
|
||||
* % if(ctx.condition2) {
|
||||
* Hello
|
||||
* % }
|
||||
* % if(ctx.condition3) {
|
||||
* World
|
||||
* % }
|
||||
* % }
|
||||
*/
|
||||
function createTemplate() { container(0); }
|
||||
|
||||
function updateTemplate() {
|
||||
containerRefreshStart(0);
|
||||
{
|
||||
if (ctx.condition) {
|
||||
let rf1 = embeddedViewStart(1);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
{ container(0); }
|
||||
{ container(1); }
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
containerRefreshStart(0);
|
||||
{
|
||||
if (ctx.condition2) {
|
||||
let rf2 = embeddedViewStart(2);
|
||||
{
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
text(0, 'Hello');
|
||||
}
|
||||
}
|
||||
embeddedViewEnd();
|
||||
}
|
||||
}
|
||||
containerRefreshEnd();
|
||||
containerRefreshStart(1);
|
||||
{
|
||||
if (ctx.condition3) {
|
||||
let rf2 = embeddedViewStart(2);
|
||||
{
|
||||
if (rf2 & RenderFlags.Create) {
|
||||
text(0, 'World');
|
||||
}
|
||||
}
|
||||
embeddedViewEnd();
|
||||
}
|
||||
}
|
||||
containerRefreshEnd();
|
||||
}
|
||||
}
|
||||
embeddedViewEnd();
|
||||
}
|
||||
}
|
||||
containerRefreshEnd();
|
||||
}
|
||||
|
||||
const fixture = new TemplateFixture(createTemplate, updateTemplate);
|
||||
expect(fixture.html).toEqual('World');
|
||||
|
||||
ctx.condition2 = true;
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('HelloWorld');
|
||||
});
|
||||
|
||||
it('should work with adjacent if blocks managing views in the same container', () => {
|
||||
|
||||
const ctx = {condition1: true, condition2: true, condition3: true};
|
||||
|
|
|
@ -361,7 +361,7 @@ describe('query', () => {
|
|||
expect(isViewContainerRef(qList.first)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should no longer read ElementRef with a native element pointing to comment DOM node from containers',
|
||||
it('should read ElementRef with a native element pointing to comment DOM node from containers',
|
||||
() => {
|
||||
/**
|
||||
* <ng-template #foo></ng-template>
|
||||
|
@ -383,7 +383,8 @@ describe('query', () => {
|
|||
const cmptInstance = renderComponent(Cmpt);
|
||||
const qList = (cmptInstance.query as QueryList<any>);
|
||||
expect(qList.length).toBe(1);
|
||||
expect(qList.first.nativeElement).toBe(null);
|
||||
expect(isElementRef(qList.first)).toBeTruthy();
|
||||
expect(qList.first.nativeElement.nodeType).toBe(8); // Node.COMMENT_NODE = 8
|
||||
});
|
||||
|
||||
it('should read TemplateRef from container nodes by default', () => {
|
||||
|
|
|
@ -32,11 +32,7 @@ export abstract class BaseFixture {
|
|||
/**
|
||||
* Current state of rendered HTML.
|
||||
*/
|
||||
get html(): string {
|
||||
return (this.hostElement as any as Element)
|
||||
.innerHTML.replace(/ style=""/g, '')
|
||||
.replace(/ class=""/g, '');
|
||||
}
|
||||
get html(): string { return toHtml(this.hostElement as any as Element); }
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
@ -223,9 +219,10 @@ export function toHtml<T>(componentOrElement: T | RElement): string {
|
|||
} else {
|
||||
return stringifyElement(componentOrElement)
|
||||
.replace(/^<div host="">/, '')
|
||||
.replace(/^<div fixture="mark">/, '')
|
||||
.replace(/<\/div>$/, '')
|
||||
.replace(' style=""', '')
|
||||
.replace(/<!--[\w]*-->/g, '');
|
||||
.replace(/<!--container-->/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -595,7 +595,7 @@ describe('ViewContainerRef', () => {
|
|||
expect(fixture.html).toEqual('<p vcref=""></p>ABC');
|
||||
|
||||
// The DOM is manually modified here to ensure that the text node is actually moved
|
||||
fixture.hostElement.childNodes[1].nodeValue = '**A**';
|
||||
fixture.hostElement.childNodes[2].nodeValue = '**A**';
|
||||
expect(fixture.html).toEqual('<p vcref=""></p>**A**BC');
|
||||
|
||||
let viewRef = directiveInstance !.vcref.get(0);
|
||||
|
|
Loading…
Reference in New Issue