fix(ivy): allow root components to inject ViewContainerRef (#26682)

PR Close #26682
This commit is contained in:
Pawel Kozlowski
2018-10-17 11:01:35 +02:00
committed by Matias Niemelä
parent 578e4c7642
commit ede65dbede
12 changed files with 175 additions and 14 deletions

View File

@ -71,6 +71,9 @@ export interface ProceduralRenderer3 {
removeChild(parent: RElement, oldChild: RNode): void;
selectRootElement(selectorOrNode: string|any): RElement;
parentNode(node: RNode): RElement|null;
nextSibling(node: RNode): RNode|null;
setAttribute(el: RElement, name: string, value: string, namespace?: string|null): void;
removeAttribute(el: RElement, name: string, namespace?: string|null): void;
addClass(el: RElement, name: string): void;
@ -99,6 +102,10 @@ export const domRendererFactory3: RendererFactory3 = {
/** Subset of API needed for appending elements and text nodes. */
export interface RNode {
parentNode: RNode|null;
nextSibling: RNode|null;
removeChild(oldChild: RNode): void;
/**

View File

@ -15,7 +15,7 @@ import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {CLEANUP, CONTAINER_INDEX, 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 {getNativeByTNode, isLContainer, readElementValue, stringify} from './util';
import {getNativeByTNode, isLContainer, isRootView, readElementValue, stringify} from './util';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
@ -501,6 +501,13 @@ function executePipeOnDestroys(viewData: LViewData): void {
export function getRenderParent(tNode: TNode, currentView: LViewData): 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));
}
const hostTNode = currentView[HOST_NODE];
const tNodeParent = tNode.parent;
@ -598,7 +605,7 @@ export function canInsertNativeNode(tNode: TNode, currentView: LViewData): boole
* This is a utility function that can be used when native nodes were determined - it abstracts an
* actual renderer being used.
*/
function nativeInsertBefore(
export function nativeInsertBefore(
renderer: Renderer3, parent: RElement, child: RNode, beforeNode: RNode | null): void {
if (isProceduralRenderer(renderer)) {
renderer.insertBefore(parent, child, beforeNode);
@ -607,6 +614,20 @@ function nativeInsertBefore(
}
}
/**
* Returns a native parent of a given native node.
*/
export function nativeParentNode(renderer: Renderer3, node: RNode): RElement|null {
return (isProceduralRenderer(renderer) ? renderer.parentNode(node) : node.parentNode) as RElement;
}
/**
* Returns a native sibling of a given native node.
*/
export function nativeNextSibling(renderer: Renderer3, node: RNode): RNode|null {
return isProceduralRenderer(renderer) ? renderer.nextSibling(node) : node.nextSibling;
}
/**
* Appends the `child` element to the `parent`.
*
@ -632,7 +653,7 @@ export function appendChild(
renderer, lContainer[RENDER_PARENT] !, childEl,
getBeforeNodeForView(index, views, lContainer[NATIVE]));
} else if (parentTNode.type === TNodeType.ElementContainer) {
const renderParent: RElement = getRenderParent(childTNode, currentView) !;
const renderParent = getRenderParent(childTNode, currentView) !;
nativeInsertBefore(renderer, renderParent, childEl, parentEl);
} else {
isProceduralRenderer(renderer) ? renderer.appendChild(parentEl !as RElement, childEl) :

View File

@ -134,6 +134,10 @@ export function isLContainer(value: RElement | RComment | LContainer | StylingCo
return Array.isArray(value) && typeof value[ACTIVE_INDEX] === 'number';
}
export function isRootView(target: LViewData): boolean {
return (target[FLAGS] & LViewFlags.IsRoot) !== 0;
}
/**
* Retrieve the root view from any component by walking the parent `LViewData` until
* reaching the root `LViewData`.

View File

@ -26,9 +26,9 @@ import {LQueries} from './interfaces/query';
import {RComment, RElement, Renderer3, isProceduralRenderer} from './interfaces/renderer';
import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, getRenderParent, insertView, removeView} from './node_manipulation';
import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode, removeView} from './node_manipulation';
import {getPreviousOrParentTNode, getRenderer, getViewData} from './state';
import {getComponentViewByIndex, getNativeByTNode, getParentInjectorTNode, getParentInjectorView, hasParentInjector, isComponent, isLContainer} from './util';
import {getComponentViewByIndex, getNativeByTNode, getParentInjectorTNode, getParentInjectorView, hasParentInjector, isComponent, isLContainer, isRootView} from './util';
import {ViewRef} from './view_ref';
@ -307,12 +307,26 @@ export function createContainerRef(
lContainer = slotValue;
lContainer[ACTIVE_INDEX] = -1;
} else {
const comment = hostView[RENDERER].createComment(ngDevMode ? 'container' : '');
const commentNode = hostView[RENDERER].createComment(ngDevMode ? 'container' : '');
ngDevMode && ngDevMode.rendererCreateComment++;
hostView[hostTNode.index] = lContainer =
createLContainer(slotValue, hostTNode, hostView, comment, true);
appendChild(comment, hostTNode, hostView);
// A container can be created on the root (topmost / bootstrapped) component and in this case we
// can't use LTree to insert container's marker node (both parent of a comment node and the
// commend node itself is located outside of elements hold by LTree). In this specific case we
// use low-level DOM manipulation to insert container's marker (comment) node.
if (isRootView(hostView)) {
const renderer = hostView[RENDERER];
const hostNative = getNativeByTNode(hostTNode, hostView) !;
const parentOfHostNative = nativeParentNode(renderer, hostNative);
nativeInsertBefore(
renderer, parentOfHostNative !, commentNode, nativeNextSibling(renderer, hostNative));
} else {
appendChild(commentNode, hostTNode, hostView);
}
hostView[hostTNode.index] = lContainer =
createLContainer(slotValue, hostTNode, hostView, commentNode, true);
addToViewTree(hostView, hostTNode.index as number, lContainer);
}