fix(ivy): remove DOM nodes from their real parent vs saved parent (#28455)

Currently, DOM node removal called `removeChild` on the saved parent
node when destroying a component. However, this will fail if the
component has been manually moved in the DOM. This change makes the
removal always use the node's real `parentNode` and ignore the provided
`parent`.

PR Close #28455
This commit is contained in:
Jeremy Elbourn
2019-02-01 15:38:42 -08:00
committed by Matias Niemelä
parent 5a2c3ff8b5
commit 89eac702b5
5 changed files with 84 additions and 21 deletions

View File

@ -19,7 +19,7 @@ import {RComment, RElement} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {StylingContext} from './interfaces/styling';
import {BINDING_INDEX, HEADER_OFFSET, HOST_NODE, LView, RENDERER, TVIEW, TView} from './interfaces/view';
import {appendChild, createTextNode, removeChild} from './node_manipulation';
import {appendChild, createTextNode, removeNode as removeRNode} from './node_manipulation';
import {getIsParent, getLView, getPreviousOrParentTNode, setIsParent, setPreviousOrParentTNode} from './state';
import {NO_CHANGE} from './tokens';
import {addAllToArray, getNativeByIndex, getNativeByTNode, getTNode, isLContainer, renderStringify} from './util';
@ -830,7 +830,7 @@ function removeNode(index: number, viewData: LView) {
const removedPhTNode = getTNode(index, viewData);
const removedPhRNode = getNativeByIndex(index, viewData);
if (removedPhRNode) {
removeChild(removedPhTNode, removedPhRNode, viewData);
removeRNode(removedPhTNode, removedPhRNode, viewData);
}
removedPhTNode.detached = true;
@ -840,7 +840,7 @@ function removeNode(index: number, viewData: LView) {
if (isLContainer(slotValue)) {
const lContainer = slotValue as LContainer;
if (removedPhTNode.type !== TNodeType.Container) {
removeChild(removedPhTNode, lContainer[NATIVE], viewData);
removeRNode(removedPhTNode, lContainer[NATIVE], viewData);
}
}
}

View File

@ -181,7 +181,7 @@ function executeNodeAction(
if (action === WalkTNodeTreeAction.Insert) {
nativeInsertBefore(renderer, parent !, node, beforeNode || null);
} else if (action === WalkTNodeTreeAction.Detach) {
nativeRemoveChild(renderer, parent !, node, isComponent(tNode));
nativeRemoveChild(renderer, node, isComponent(tNode));
} else if (action === WalkTNodeTreeAction.Destroy) {
ngDevMode && ngDevMode.rendererDestroyNode++;
(renderer as ProceduralRenderer3).destroyNode !(node);
@ -593,13 +593,19 @@ function nativeAppendOrInsertBefore(
}
}
/**
* Removes a native child node from a given native parent node.
*/
/** Removes a node from the DOM. */
export function nativeRemoveChild(
renderer: Renderer3, parent: RElement, child: RNode, isHostElement?: boolean): void {
isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, child, isHostElement) :
parent.removeChild(child);
renderer: Renderer3, child: RNode, isHostElement?: boolean): void {
if (isProceduralRenderer(renderer)) {
const renderParent = renderer.parentNode(child);
if (renderParent) {
renderer.removeChild(renderParent, child, isHostElement);
}
} else {
// We intentionally don't use the given parent node since it may no longer
// match the state of the DOM (if the child node has been manually moved).
child.parentNode && child.parentNode.removeChild(child);
}
}
/**
@ -692,14 +698,9 @@ export function getBeforeNodeForView(index: number, views: LView[], containerNat
* @param childTNode The TNode of the child to remove
* @param childEl The child that should be removed
* @param currentView The current LView
* @returns Whether or not the child was removed
*/
export function removeChild(childTNode: TNode, childEl: RNode, currentView: LView): void {
const parentNative = getRenderParent(childTNode, currentView);
// We only remove the element if it already has a render parent.
if (parentNative) {
nativeRemoveChild(currentView[RENDERER], parentNative, childEl);
}
export function removeNode(childTNode: TNode, childEl: RNode, currentView: LView): void {
nativeRemoveChild(currentView[RENDERER], childEl);
}
/**