feat(ivy): add basic support for ng-container (#25227)

This commit adds basic support for <ng-container> - most of the
functionality should work as long as <ng-container> is a child of
a regular element.

PR Close #25227
This commit is contained in:
Pawel Kozlowski
2018-07-26 17:22:41 +02:00
committed by Kara Erickson
parent 4f741e74e1
commit 28c7a4efbc
9 changed files with 236 additions and 26 deletions

View File

@ -21,7 +21,7 @@ import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLCo
import {VIEWS} from './interfaces/container';
import {DirectiveDefInternal, RenderFlags} from './interfaces/definition';
import {LInjector} from './interfaces/injector';
import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
import {AttributeMarker, LContainerNode, LElementContainerNode, LElementNode, LNode, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
import {LQueries, QueryReadType} from './interfaces/query';
import {Renderer3} from './interfaces/renderer';
import {DECLARATION_VIEW, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
@ -91,7 +91,8 @@ export function bloomAdd(injector: LInjector, type: Type<any>): void {
export function getOrCreateNodeInjector(): LInjector {
ngDevMode && assertPreviousIsParent();
return getOrCreateNodeInjectorForNode(getPreviousOrParentNode() as LElementNode | LContainerNode);
return getOrCreateNodeInjectorForNode(
getPreviousOrParentNode() as LElementNode | LElementContainerNode | LContainerNode);
}
/**
@ -100,7 +101,8 @@ export function getOrCreateNodeInjector(): LInjector {
* @param node for which an injector should be retrieved / created.
* @returns Node injector
*/
export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNode): LInjector {
export function getOrCreateNodeInjectorForNode(
node: LElementNode | LElementContainerNode | LContainerNode): LInjector {
const nodeInjector = node.nodeInjector;
const parent = getParentLNode(node);
const parentInjector = parent && parent.nodeInjector;
@ -637,7 +639,8 @@ class ViewContainerRef implements viewEngine.ViewContainerRef {
private _viewRefs: viewEngine.ViewRef[] = [];
constructor(
private _lContainerNode: LContainerNode, private _hostNode: LElementNode|LContainerNode) {}
private _lContainerNode: LContainerNode,
private _hostNode: LElementNode|LElementContainerNode|LContainerNode) {}
get element(): ElementRef {
const injector = getOrCreateNodeInjectorForNode(this._hostNode);

View File

@ -18,7 +18,7 @@ import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} fro
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {LInjector} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
@ -419,6 +419,9 @@ export function createLNode(
export function createLNode(
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
lProjection: null): LProjectionNode;
export function createLNode(
index: number, type: TNodeType.ElementContainer, native: RComment, name: null,
attrs: TAttributes | null, data: null): LElementContainerNode;
export function createLNode(
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
attrs: TAttributes | null, state?: null | LViewData | LContainer): LElementNode&LTextNode&
@ -695,6 +698,50 @@ export function element(
elementEnd();
}
/**
* Creates a logical container for other nodes (<ng-container>) backed by a comment node in the DOM.
* The instruction must later be followed by `elementContainerEnd()` call.
*
* @param index Index of the element in the LViewData array
* @param attrs Set of attributes to be used when matching directives.
* @param localRefs A set of local reference bindings on the element.
*
* Even if this instruction accepts a set of attributes no actual attribute values are propoagted to
* the DOM (as a comment node can't have attributes). Attributes are here only for directive
* matching purposes and setting initial inputs of directives.
*/
export function elementContainerStart(
index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void {
ngDevMode &&
assertEqual(viewData[BINDING_INDEX], -1, 'elements should be created before any bindings');
ngDevMode && ngDevMode.rendererCreateComment++;
const native = renderer.createComment(ngDevMode ? 'ng-container' : '');
ngDevMode && assertDataInRange(index - 1);
const node: LElementContainerNode =
createLNode(index, TNodeType.ElementContainer, native, null, attrs || null, null);
appendChild(getParentLNode(node), native, viewData);
createDirectivesAndLocals(localRefs);
}
/** Mark the end of the <ng-container>. */
export function elementContainerEnd(): void {
if (isParent) {
isParent = false;
} else {
ngDevMode && assertHasParent();
previousOrParentNode = getParentLNode(previousOrParentNode) as LElementContainerNode;
}
ngDevMode && assertNodeType(previousOrParentNode, TNodeType.ElementContainer);
const queries = previousOrParentNode.queries;
queries && queries.addNode(previousOrParentNode);
queueLifecycleHooks(previousOrParentNode.tNode.flags, tView);
}
/**
* Create DOM element. The instruction must later be followed by `elementEnd()` call.
*
@ -1086,6 +1133,7 @@ export function hostElement(
export function listener(
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
ngDevMode && assertPreviousIsParent();
ngDevMode && assertNodeOfPossibleTypes(previousOrParentNode, TNodeType.Element);
const node = previousOrParentNode;
const native = node.native as RElement;
ngDevMode && ngDevMode.rendererAddEventListener++;
@ -1779,6 +1827,7 @@ export function container(
const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !;
const lContainer = createLContainer(currentParent, viewData);
ngDevMode && ngDevMode.rendererCreateComment++;
const comment = renderer.createComment(ngDevMode ? 'container' : '');
const node =
createLNode(index, TNodeType.Container, comment, tagName || null, attrs || null, lContainer);

View File

@ -11,7 +11,7 @@ import {ElementRef} from '../../linker/element_ref';
import {TemplateRef} from '../../linker/template_ref';
import {ViewContainerRef} from '../../linker/view_container_ref';
import {LContainerNode, LElementNode} from './node';
import {LContainerNode, LElementContainerNode, LElementNode} from './node';
export interface LInjector {
/**
@ -26,7 +26,7 @@ export interface LInjector {
* for DI to retrieve a directive from the data array if injector indicates
* it is there.
*/
readonly node: LElementNode|LContainerNode;
readonly node: LElementNode|LElementContainerNode|LContainerNode;
/**
* The following bloom filter determines whether a directive is available

View File

@ -21,11 +21,12 @@ import {LViewData, TView} from './view';
* on how to map a particular set of bits in LNode.flags to the node type.
*/
export const enum TNodeType {
Container = 0b00,
Projection = 0b01,
View = 0b10,
Element = 0b11,
ViewOrElement = 0b10,
Container = 0b000,
Projection = 0b001,
View = 0b010,
Element = 0b011,
ViewOrElement = 0b010,
ElementContainer = 0b100,
}
/**
@ -118,6 +119,13 @@ export interface LElementNode extends LNode {
readonly data: LViewData|null;
}
/** LNode representing <ng-container>. */
export interface LElementContainerNode extends LNode {
/** The DOM comment associated with this node. */
readonly native: RComment;
readonly data: null;
}
/** LNode representing a #text node. */
export interface LTextNode extends LNode {
/** The text node associated with this node. */

View File

@ -29,6 +29,7 @@ declare global {
rendererDestroyNode: number;
rendererMoveNode: number;
rendererRemoveNode: number;
rendererCreateComment: number;
}
}
@ -61,6 +62,7 @@ export function ngDevModeResetPerfCounters() {
rendererDestroyNode: 0,
rendererMoveNode: 0,
rendererRemoveNode: 0,
rendererCreateComment: 0,
};
}

View File

@ -9,7 +9,7 @@
import {assertDefined} from './assert';
import {callHooks} from './hooks';
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
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, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
@ -38,11 +38,14 @@ export function getChildLNode(node: LNode): LNode|null {
}
/** Retrieves the parent LNode of a given node. */
export function getParentLNode(node: LContainerNode | LElementNode | LTextNode | LProjectionNode):
LElementNode|LViewNode;
export function getParentLNode(
node: LContainerNode | LElementNode | LElementContainerNode | LTextNode |
LProjectionNode): LElementNode|LElementContainerNode|LViewNode;
export function getParentLNode(node: LViewNode): LContainerNode|null;
export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null;
export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null {
export function getParentLNode(node: LNode): LElementNode|LElementContainerNode|LContainerNode|
LViewNode|null;
export function getParentLNode(node: LNode): LElementNode|LElementContainerNode|LContainerNode|
LViewNode|null {
if (node.tNode.index === -1 && node.tNode.type === TNodeType.View) {
// This is a dynamically created view inside a dynamic container.
// If the host index is -1, the view has not yet been inserted, so it has no parent.
@ -518,9 +521,10 @@ function executePipeOnDestroys(viewData: LViewData): void {
*/
export function canInsertNativeNode(parent: LNode, currentView: LViewData): boolean {
// We can only insert into a Component or View. Any other type should be an Error.
ngDevMode && assertNodeOfPossibleTypes(parent, TNodeType.Element, TNodeType.View);
ngDevMode && assertNodeOfPossibleTypes(
parent, TNodeType.Element, TNodeType.ElementContainer, TNodeType.View);
if (parent.tNode.type === TNodeType.Element) {
if (parent.tNode.type === TNodeType.Element || parent.tNode.type === TNodeType.ElementContainer) {
// Parent is an element.
if (parent.view !== currentView) {
// If the Parent view is not the same as current view than we are inserting across
@ -584,6 +588,12 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: LVi
isProceduralRenderer(renderer) ?
renderer.insertBefore(renderParent !.native, child, beforeNode) :
renderParent !.native.insertBefore(child, beforeNode, true);
} else if (parent.tNode.type === TNodeType.ElementContainer) {
const beforeNode = parent.native;
const renderParent = getParentLNode(parent) as LElementNode;
isProceduralRenderer(renderer) ?
renderer.insertBefore(renderParent !.native, child, beforeNode) :
renderParent !.native.insertBefore(child, beforeNode, true);
} else {
isProceduralRenderer(renderer) ? renderer.appendChild(parent.native !as RElement, child) :
parent.native !.appendChild(child);
@ -621,8 +631,9 @@ export function removeChild(parent: LNode, child: RNode | null, currentView: LVi
* @param currentView Current LView
*/
export function appendProjectedNode(
node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode | LViewNode,
currentView: LViewData, renderParent: LElementNode): void {
node: LElementNode | LElementContainerNode | LTextNode | LContainerNode,
currentParent: LElementNode | LElementContainerNode | LViewNode, currentView: LViewData,
renderParent: LElementNode): void {
appendChild(currentParent, node.native, currentView);
if (node.tNode.type === TNodeType.Container) {
// The node we are adding is a container and we are adding it to an element which