refactor(ivy): merge canInsertNativeNode and getParentNative (#28011)

Previously the canInsertNativeNode and getRenderParent functions had almost
_exaclty_ the same logic. What was worse that getRenderParent was calling
canInsertNativeNode thus executing the same, non-trivial logic twice.

This commit merges canInsertNativeNode and getRenderParent into one function.
Now getRenderParent will return a native parent or null if a node can't be
inserted (content projection, root of a view that is not inserted etc.).

PR Close #28011
This commit is contained in:
Pawel Kozlowski 2019-01-09 16:33:25 +01:00 committed by Andrew Kushnir
parent 6f9881f85f
commit e1e4887feb
3 changed files with 41 additions and 113 deletions

View File

@ -18,23 +18,6 @@ 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. */
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.
*/
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) :
null;
}
export function getLContainer(tNode: TViewNode, embeddedView: LView): LContainer|null { export function getLContainer(tNode: TViewNode, embeddedView: LView): LContainer|null {
if (tNode.index === -1) { if (tNode.index === -1) {
// This is a dynamically created view inside a dynamic container. // This is a dynamically created view inside a dynamic container.
@ -485,51 +468,8 @@ function executeOnDestroys(view: LView): void {
} }
} }
function getRenderParent(tNode: TNode, currentView: LView): RElement|null {
if (canInsertNativeNode(tNode, currentView)) {
// If we are asked for a render parent of the root component we need to do low-level DOM
// operation as LTree doesn't exist above the topmost host node. We might need to find a render
// parent of the topmost host node if the root component injects ViewContainerRef.
if (isRootView(currentView)) {
return nativeParentNode(currentView[RENDERER], getNativeByTNode(tNode, currentView));
}
// 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 ?
getContainerRenderParent(hostTNode as TViewNode, currentView) :
getParentNative(tNode, currentView) as RElement;
}
return null;
}
function canInsertNativeChildOfElement(tNode: TElementNode): boolean {
// Component's content nodes are not inserted immediately
// because they will be projected, and so doing insert at this point would be wasteful.
// Since the projection would than move it to its final destination.
return !(tNode.flags & TNodeFlags.isComponent);
}
/** /**
* We might delay insertion of children for a given view if it is disconnected. * Returns a native element if a node can be inserted into the given parent.
* This might happen for 2 main reasons:
* - view is not inserted into any container (view was created but not inserted yet)
* - view is inserted into a container but the container itself is not inserted into the DOM
* (container might be part of projection or child of a view that is not inserted yet).
*
* In other words we can insert children of a given view if this view was inserted into a container
* and
* the container itself has its render parent determined.
*/
function canInsertNativeChildOfView(viewTNode: TViewNode, view: LView): boolean {
return getContainerRenderParent(viewTNode, view) != null;
}
/**
* Returns whether a native element can be inserted into the given parent.
* *
* There are two reasons why we may not be able to insert a element immediately. * There are two reasons why we may not be able to insert a element immediately.
* - Projection: When creating a child content element of a component, we have to skip the * - Projection: When creating a child content element of a component, we have to skip the
@ -539,19 +479,15 @@ function canInsertNativeChildOfView(viewTNode: TViewNode, view: LView): boolean
* parent container, which itself is disconnected. For example the parent container is part * parent container, which itself is disconnected. For example the parent container is part
* of a View which has not be inserted or is mare for projection but has not been inserted * of a View which has not be inserted or is mare for projection but has not been inserted
* into destination. * into destination.
*
*
* @param tNode The tNode of the node that we want to insert.
* @param currentView Current LView being processed.
* @return boolean Whether the node should be inserted now (or delayed until later).
*/ */
function canInsertNativeNode(tNode: TNode, currentView: LView): boolean { function getRenderParent(tNode: TNode, currentView: LView): RElement|null {
// Nodes of the top-most view can be inserted eagerly // Nodes of the top-most view can be inserted eagerly.
if (isRootView(currentView)) { if (isRootView(currentView)) {
return true; return nativeParentNode(currentView[RENDERER], getNativeByTNode(tNode, currentView));
} }
// Skip over element and ICU containers as those are represented by a comment node and
// can't be used as a render parent.
const parent = getHighestElementOrICUContainer(tNode).parent; const parent = getHighestElementOrICUContainer(tNode).parent;
// If the parent is null, then we are inserting across views: either into an embedded view or a // If the parent is null, then we are inserting across views: either into an embedded view or a
@ -559,24 +495,44 @@ function canInsertNativeNode(tNode: TNode, currentView: LView): boolean {
if (parent == null) { if (parent == null) {
const hostTNode = currentView[HOST_NODE] !; const hostTNode = currentView[HOST_NODE] !;
if (hostTNode.type === TNodeType.View) { if (hostTNode.type === TNodeType.View) {
// We are inserting a root element of an embedded view - this should only be possible if the // We are inserting a root element of an embedded view We might delay insertion of children
// embedded view in question is already inserted into a container and the container itself is // for a given view if it is disconnected. This might happen for 2 main reasons:
// inserted into the DOM. // - view is not inserted into any container(view was created but not inserted yet)
return canInsertNativeChildOfView(hostTNode as TViewNode, currentView); // - view is inserted into a container but the container itself is not inserted into the DOM
// (container might be part of projection or child of a view that is not inserted yet).
// In other words we can insert children of a given view if this view was inserted into a
// container and the container itself has its render parent determined.
return getContainerRenderParent(hostTNode as TViewNode, currentView);
} else { } else {
// We are inserting a root element of the component view into the component host element and // We are inserting a root element of the component view into the component host element and
// it should always be eager. // it should always be eager.
return true; return getHostNative(currentView);
} }
} else { } else {
ngDevMode && assertNodeType(parent, TNodeType.Element); ngDevMode && assertNodeType(parent, TNodeType.Element);
// We've got a parent which is an element in the current view. We just need to verify if the // We've got a parent which is an element in the current view. We just need to verify if the
// parent element is not a component (this would mean that the tNode represents a root content // parent element is not a component. Component's content nodes are not inserted immediately
// node of a component and its insertion should be delayed due to content projection). // because they will be projected, and so doing insert at this point would be wasteful.
return canInsertNativeChildOfElement(parent as TElementNode); // Since the projection would than move it to its final destination.
if (!(parent.flags & TNodeFlags.isComponent)) {
return getNativeByTNode(parent, currentView) as RElement;
} else {
return null;
}
} }
} }
/**
* Gets the native host element for a given view. Will return null if the current view does not have
* a host element.
*/
function getHostNative(currentView: LView): RElement|null {
const hostTNode = currentView[HOST_NODE];
return hostTNode && hostTNode.type === TNodeType.Element ?
(getNativeByTNode(hostTNode, currentView[PARENT] !) as RElement) :
null;
}
/** /**
* Inserts a native node before another native node for a given parent using {@link Renderer3}. * Inserts a native node before another native node for a given parent using {@link Renderer3}.
* This is a utility function that can be used when native nodes were determined - it abstracts an * This is a utility function that can be used when native nodes were determined - it abstracts an
@ -623,10 +579,10 @@ export function nativeNextSibling(renderer: Renderer3, node: RNode): RNode|null
* @param currentView The current LView * @param currentView The current LView
* @returns Whether or not the child was appended * @returns Whether or not the child was appended
*/ */
export function appendChild(childEl: RNode, childTNode: TNode, currentView: LView): boolean { export function appendChild(childEl: RNode, childTNode: TNode, currentView: LView): void {
if (canInsertNativeNode(childTNode, currentView)) { const renderParent = getRenderParent(childTNode, currentView);
if (renderParent != null) {
const renderer = currentView[RENDERER]; const renderer = currentView[RENDERER];
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) {
@ -644,9 +600,7 @@ export function appendChild(childEl: RNode, childTNode: TNode, currentView: LVie
isProceduralRenderer(renderer) ? renderer.appendChild(renderParent, childEl) : isProceduralRenderer(renderer) ? renderer.appendChild(renderParent, childEl) :
renderParent.appendChild(childEl); renderParent.appendChild(childEl);
} }
return true;
} }
return false;
} }
/** /**
@ -681,14 +635,12 @@ export function getBeforeNodeForView(index: number, views: LView[], containerNat
* @param currentView The current LView * @param currentView The current LView
* @returns Whether or not the child was removed * @returns Whether or not the child was removed
*/ */
export function removeChild(childTNode: TNode, childEl: RNode, currentView: LView): boolean { export function removeChild(childTNode: TNode, childEl: RNode, currentView: LView): void {
// We only remove the element if not in View or not projected. const parentNative = getRenderParent(childTNode, currentView);
if (canInsertNativeNode(childTNode, currentView)) { // We only remove the element if it already has a render parent.
const parentNative = getRenderParent(childTNode, currentView) !; if (parentNative) {
nativeRemoveChild(currentView[RENDERER], parentNative, childEl); nativeRemoveChild(currentView[RENDERER], parentNative, childEl);
return true;
} }
return false;
} }
/** /**

View File

@ -155,15 +155,6 @@
{ {
"name": "callHooks" "name": "callHooks"
}, },
{
"name": "canInsertNativeChildOfElement"
},
{
"name": "canInsertNativeChildOfView"
},
{
"name": "canInsertNativeNode"
},
{ {
"name": "checkNoChangesMode" "name": "checkNoChangesMode"
}, },
@ -299,9 +290,6 @@
{ {
"name": "getParentInjectorViewOffset" "name": "getParentInjectorViewOffset"
}, },
{
"name": "getParentNative"
},
{ {
"name": "getPipeDef" "name": "getPipeDef"
}, },

View File

@ -410,15 +410,6 @@
{ {
"name": "callHooks" "name": "callHooks"
}, },
{
"name": "canInsertNativeChildOfElement"
},
{
"name": "canInsertNativeChildOfView"
},
{
"name": "canInsertNativeNode"
},
{ {
"name": "checkNoChanges" "name": "checkNoChanges"
}, },
@ -749,9 +740,6 @@
{ {
"name": "getParentInjectorViewOffset" "name": "getParentInjectorViewOffset"
}, },
{
"name": "getParentNative"
},
{ {
"name": "getParentState" "name": "getParentState"
}, },