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;
/** 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 {
if (tNode.parent == null) {
return getHostNative(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 !;
function getParentNative(tNode: TNode, currentView: LView): RElement|RComment|null {
return tNode.parent == null ? getHostNative(currentView) :
getNativeByTNode(tNode.parent, currentView);
}
/**
* 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.
*/
export function getHostNative(currentView: LView): RElement|null {
function getHostNative(currentView: LView): RElement|null {
const hostTNode = currentView[HOST_NODE] as TElementNode;
return hostTNode && hostTNode.type !== TNodeType.View ?
(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));
}
const tNodeParent = getFirstNonICUParent(tNode);
if (tNodeParent != null && tNodeParent.type === TNodeType.ElementContainer) {
tNode = getHighestElementContainer(tNodeParent);
}
// skip over element and ICU containers as those are represented by a comment node and
// can't be used as a render parent
tNode = getHighestElementOrICUContainer(tNode);
const hostTNode = currentView[HOST_NODE];
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 {
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 && parent.type === TNodeType.View) {
@ -633,7 +611,7 @@ export function nativeInsertBefore(
*/
export function nativeRemoveChild(renderer: Renderer3, parent: RElement, child: RNode): void {
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 {
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
const renderer = currentView[RENDERER];
const parentEl = getParentNative(childTNode, currentView);
const renderParent = getRenderParent(childTNode, currentView) !;
const parentTNode: TNode = childTNode.parent || currentView[HOST_NODE] !;
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 index = views.indexOf(currentView);
nativeInsertBefore(
renderer, lContainer[RENDER_PARENT] !, childEl,
getBeforeNodeForView(index, views, lContainer[NATIVE]));
} else if (parentTNode.type === TNodeType.ElementContainer) {
const renderParent = getRenderParent(childTNode, currentView) !;
const ngContainerAnchorNode = getNativeByTNode(parentTNode, currentView);
nativeInsertBefore(renderer, renderParent, childEl, ngContainerAnchorNode);
} else if (parentTNode.type === TNodeType.IcuContainer) {
const renderParent = getRenderParent(childTNode, currentView) !;
const icuAnchorNode = getNativeByTNode(parentTNode, currentView);
nativeInsertBefore(renderer, renderParent, childEl, icuAnchorNode);
renderer, renderParent, childEl, getBeforeNodeForView(index, views, lContainer[NATIVE]));
} else if (
parentTNode.type === TNodeType.ElementContainer ||
parentTNode.type === TNodeType.IcuContainer) {
const anchorNode = getNativeByTNode(parentTNode, currentView);
nativeInsertBefore(renderer, renderParent, childEl, anchorNode);
} else {
isProceduralRenderer(renderer) ? renderer.appendChild(parentEl !as RElement, childEl) :
parentEl !.appendChild(childEl);
isProceduralRenderer(renderer) ? renderer.appendChild(renderParent, childEl) :
renderParent.appendChild(childEl);
}
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
* @returns tNode The TNode of the highest level ng-container
* @param tNode The starting TNode for which we should skip element and ICU containers
* @returns The TNode of the highest level ICU container or element container
*/
function getHighestElementContainer(ngContainer: TNode): TNode {
while (ngContainer.parent != null && ngContainer.parent.type === TNodeType.ElementContainer) {
ngContainer = ngContainer.parent;
function getHighestElementOrICUContainer(tNode: TNode): TNode {
while (tNode.parent != null && (tNode.parent.type === TNodeType.ElementContainer ||
tNode.parent.type === TNodeType.IcuContainer)) {
tNode = tNode.parent;
}
return ngContainer;
return tNode;
}
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 {
// We only remove the element if not in View or not projected.
if (childEl !== null && canInsertNativeNode(childTNode, currentView)) {
const parentNative = getParentNative(childTNode, currentView) !as RElement;
const parentNative = getRenderParent(childTNode, currentView) !;
nativeRemoveChild(currentView[RENDERER], parentNative, childEl);
return true;
}

View File

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

View File

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

View File

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