refactor(ivy): unify native node handling around render parent and element / ICU containers (#27925)

PR Close #27925
This commit is contained in:
Pawel Kozlowski 2019-01-04 17:17:50 +01:00 committed by Kara Erickson
parent 8a3cebde8b
commit e08feb7e54
4 changed files with 51 additions and 78 deletions

View File

@ -19,31 +19,16 @@ import {findComponentView, getNativeByTNode, isLContainer, isRootView, readEleme
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
/** Retrieves the native node (element or a comment) for the parent of a given node. */ /** Retrieves the native node (element or a comment) for the parent of a given node. */
export function getParentNative(tNode: TNode, currentView: LView): RElement|RComment|null { function getParentNative(tNode: TNode, currentView: LView): RElement|RComment|null {
if (tNode.parent == null) { return tNode.parent == null ? getHostNative(currentView) :
return getHostNative(currentView); getNativeByTNode(tNode.parent, currentView);
} else {
const parentTNode = getFirstNonICUParent(tNode);
return getNativeByTNode(parentTNode, currentView);
}
}
/**
* Get the first parent of a node that isn't an IcuContainer TNode
*/
function getFirstNonICUParent(tNode: TNode): TNode {
let parent = tNode.parent;
while (parent && parent.type === TNodeType.IcuContainer) {
parent = parent.parent;
}
return parent !;
} }
/** /**
* Gets the host element given a view. Will return null if the current view is an embedded view, * Gets the host element given a view. Will return null if the current view is an embedded view,
* which does not have a host element. * which does not have a host element.
*/ */
export function getHostNative(currentView: LView): RElement|null { function getHostNative(currentView: LView): RElement|null {
const hostTNode = currentView[HOST_NODE] as TElementNode; const hostTNode = currentView[HOST_NODE] as TElementNode;
return hostTNode && hostTNode.type !== TNodeType.View ? return hostTNode && hostTNode.type !== TNodeType.View ?
(getNativeByTNode(hostTNode, currentView[PARENT] !) as RElement) : (getNativeByTNode(hostTNode, currentView[PARENT] !) as RElement) :
@ -518,10 +503,9 @@ export function getRenderParent(tNode: TNode, currentView: LView): RElement|null
return nativeParentNode(currentView[RENDERER], getNativeByTNode(tNode, currentView)); return nativeParentNode(currentView[RENDERER], getNativeByTNode(tNode, currentView));
} }
const tNodeParent = getFirstNonICUParent(tNode); // skip over element and ICU containers as those are represented by a comment node and
if (tNodeParent != null && tNodeParent.type === TNodeType.ElementContainer) { // can't be used as a render parent
tNode = getHighestElementContainer(tNodeParent); tNode = getHighestElementOrICUContainer(tNode);
}
const hostTNode = currentView[HOST_NODE]; const hostTNode = currentView[HOST_NODE];
return tNode.parent == null && hostTNode !.type === TNodeType.View ? return tNode.parent == null && hostTNode !.type === TNodeType.View ?
@ -593,17 +577,11 @@ function canInsertNativeChildOfView(viewTNode: TViewNode, view: LView): boolean
*/ */
export function canInsertNativeNode(tNode: TNode, currentView: LView): boolean { export function canInsertNativeNode(tNode: TNode, currentView: LView): boolean {
let currentNode = tNode; let currentNode = tNode;
let parent: TNode|null = tNode.parent; let parent: TNode|null;
currentNode = getHighestElementOrICUContainer(currentNode);
parent = currentNode.parent;
if (tNode.parent) {
if (tNode.parent.type === TNodeType.ElementContainer) {
currentNode = getHighestElementContainer(tNode);
parent = currentNode.parent;
} else if (tNode.parent.type === TNodeType.IcuContainer) {
currentNode = getFirstNonICUParent(currentNode);
parent = currentNode.parent;
}
}
if (parent === null) parent = currentView[HOST_NODE]; if (parent === null) parent = currentView[HOST_NODE];
if (parent && parent.type === TNodeType.View) { if (parent && parent.type === TNodeType.View) {
@ -633,7 +611,7 @@ export function nativeInsertBefore(
*/ */
export function nativeRemoveChild(renderer: Renderer3, parent: RElement, child: RNode): void { export function nativeRemoveChild(renderer: Renderer3, parent: RElement, child: RNode): void {
isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, child) : isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, child) :
parent !.removeChild(child); parent.removeChild(child);
} }
/** /**
@ -664,27 +642,23 @@ export function appendChild(
childEl: RNode | null = null, childTNode: TNode, currentView: LView): boolean { childEl: RNode | null = null, childTNode: TNode, currentView: LView): boolean {
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) { if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
const renderer = currentView[RENDERER]; const renderer = currentView[RENDERER];
const parentEl = getParentNative(childTNode, currentView); const renderParent = getRenderParent(childTNode, currentView) !;
const parentTNode: TNode = childTNode.parent || currentView[HOST_NODE] !; const parentTNode: TNode = childTNode.parent || currentView[HOST_NODE] !;
if (parentTNode.type === TNodeType.View) { if (parentTNode.type === TNodeType.View) {
const lContainer = getLContainer(parentTNode as TViewNode, currentView) as LContainer; const lContainer = getLContainer(parentTNode as TViewNode, currentView) !;
const views = lContainer[VIEWS]; const views = lContainer[VIEWS];
const index = views.indexOf(currentView); const index = views.indexOf(currentView);
nativeInsertBefore( nativeInsertBefore(
renderer, lContainer[RENDER_PARENT] !, childEl, renderer, renderParent, childEl, getBeforeNodeForView(index, views, lContainer[NATIVE]));
getBeforeNodeForView(index, views, lContainer[NATIVE])); } else if (
} else if (parentTNode.type === TNodeType.ElementContainer) { parentTNode.type === TNodeType.ElementContainer ||
const renderParent = getRenderParent(childTNode, currentView) !; parentTNode.type === TNodeType.IcuContainer) {
const ngContainerAnchorNode = getNativeByTNode(parentTNode, currentView); const anchorNode = getNativeByTNode(parentTNode, currentView);
nativeInsertBefore(renderer, renderParent, childEl, ngContainerAnchorNode); nativeInsertBefore(renderer, renderParent, childEl, anchorNode);
} else if (parentTNode.type === TNodeType.IcuContainer) {
const renderParent = getRenderParent(childTNode, currentView) !;
const icuAnchorNode = getNativeByTNode(parentTNode, currentView);
nativeInsertBefore(renderer, renderParent, childEl, icuAnchorNode);
} else { } else {
isProceduralRenderer(renderer) ? renderer.appendChild(parentEl !as RElement, childEl) : isProceduralRenderer(renderer) ? renderer.appendChild(renderParent, childEl) :
parentEl !.appendChild(childEl); renderParent.appendChild(childEl);
} }
return true; return true;
} }
@ -692,16 +666,17 @@ export function appendChild(
} }
/** /**
* Gets the top-level ng-container if ng-containers are nested. * Gets the top-level element or an ICU container if those containers are nested.
* *
* @param ngContainer The TNode of the starting ng-container * @param tNode The starting TNode for which we should skip element and ICU containers
* @returns tNode The TNode of the highest level ng-container * @returns The TNode of the highest level ICU container or element container
*/ */
function getHighestElementContainer(ngContainer: TNode): TNode { function getHighestElementOrICUContainer(tNode: TNode): TNode {
while (ngContainer.parent != null && ngContainer.parent.type === TNodeType.ElementContainer) { while (tNode.parent != null && (tNode.parent.type === TNodeType.ElementContainer ||
ngContainer = ngContainer.parent; tNode.parent.type === TNodeType.IcuContainer)) {
tNode = tNode.parent;
} }
return ngContainer; return tNode;
} }
export function getBeforeNodeForView(index: number, views: LView[], containerNative: RComment) { export function getBeforeNodeForView(index: number, views: LView[], containerNative: RComment) {
@ -725,7 +700,7 @@ export function getBeforeNodeForView(index: number, views: LView[], containerNat
export function removeChild(childTNode: TNode, childEl: RNode | null, currentView: LView): boolean { export function removeChild(childTNode: TNode, childEl: RNode | null, currentView: LView): boolean {
// We only remove the element if not in View or not projected. // We only remove the element if not in View or not projected.
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) { if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
const parentNative = getParentNative(childTNode, currentView) !as RElement; const parentNative = getRenderParent(childTNode, currentView) !;
nativeRemoveChild(currentView[RENDERER], parentNative, childEl); nativeRemoveChild(currentView[RENDERER], parentNative, childEl);
return true; return true;
} }

View File

@ -258,10 +258,7 @@
"name": "getDirectiveDef" "name": "getDirectiveDef"
}, },
{ {
"name": "getFirstNonICUParent" "name": "getHighestElementOrICUContainer"
},
{
"name": "getHighestElementContainer"
}, },
{ {
"name": "getHostNative" "name": "getHostNative"

View File

@ -674,14 +674,11 @@
{ {
"name": "getElementDepthCount" "name": "getElementDepthCount"
}, },
{
"name": "getFirstNonICUParent"
},
{ {
"name": "getFirstTemplatePass" "name": "getFirstTemplatePass"
}, },
{ {
"name": "getHighestElementContainer" "name": "getHighestElementOrICUContainer"
}, },
{ {
"name": "getHostNative" "name": "getHostNative"

View File

@ -713,7 +713,7 @@ describe('Runtime i18n', () => {
expect(fixture.html) expect(fixture.html)
.toEqual( .toEqual(
'<div><span>3 <span title="emails label">emails</span><!--ICU 4--></span></div>'); '<div><span>3 <span title="emails label">emails</span><!--ICU 4--></span></div>');
}); });
it('for ICU expressions inside <ng-container>', () => { it('for ICU expressions inside <ng-container>', () => {
const MSG_DIV = `{<7B>0<EFBFBD>, plural, const MSG_DIV = `{<7B>0<EFBFBD>, plural,
@ -721,20 +721,24 @@ describe('Runtime i18n', () => {
=1 {one <i>email</i>} =1 {one <i>email</i>}
other {<EFBFBD>0<EFBFBD> <span title="<22>1<EFBFBD>">emails</span>} other {<EFBFBD>0<EFBFBD> <span title="<22>1<EFBFBD>">emails</span>}
}`; }`;
const fixture = prepareFixture(() => { const fixture = prepareFixture(
elementStart(0, 'div'); { () => {
elementContainerStart(1); { elementStart(0, 'div');
i18n(2, MSG_DIV); {
} elementContainerStart(1);
elementContainerEnd(); { i18n(2, MSG_DIV); }
} elementContainerEnd();
elementEnd(); }
}, () => { elementEnd();
i18nExp(bind(0)); },
i18nApply(2); () => {
}, 3, 1); i18nExp(bind(0));
i18nExp(bind('more than one'));
i18nApply(2);
},
3, 2);
expect(fixture.html).toEqual('<div>no <b title="none">emails</b>!<!--ICU 4--></div>'); expect(fixture.html).toEqual('<div>no <b title="none">emails</b>!<!--ICU 5--></div>');
}); });
it('for nested ICU expressions', () => { it('for nested ICU expressions', () => {