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:
parent
4f741e74e1
commit
28c7a4efbc
|
@ -21,7 +21,7 @@ import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLCo
|
||||||
import {VIEWS} from './interfaces/container';
|
import {VIEWS} from './interfaces/container';
|
||||||
import {DirectiveDefInternal, RenderFlags} from './interfaces/definition';
|
import {DirectiveDefInternal, RenderFlags} from './interfaces/definition';
|
||||||
import {LInjector} from './interfaces/injector';
|
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 {LQueries, QueryReadType} from './interfaces/query';
|
||||||
import {Renderer3} from './interfaces/renderer';
|
import {Renderer3} from './interfaces/renderer';
|
||||||
import {DECLARATION_VIEW, DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
|
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 {
|
export function getOrCreateNodeInjector(): LInjector {
|
||||||
ngDevMode && assertPreviousIsParent();
|
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.
|
* @param node for which an injector should be retrieved / created.
|
||||||
* @returns Node injector
|
* @returns Node injector
|
||||||
*/
|
*/
|
||||||
export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNode): LInjector {
|
export function getOrCreateNodeInjectorForNode(
|
||||||
|
node: LElementNode | LElementContainerNode | LContainerNode): LInjector {
|
||||||
const nodeInjector = node.nodeInjector;
|
const nodeInjector = node.nodeInjector;
|
||||||
const parent = getParentLNode(node);
|
const parent = getParentLNode(node);
|
||||||
const parentInjector = parent && parent.nodeInjector;
|
const parentInjector = parent && parent.nodeInjector;
|
||||||
|
@ -637,7 +639,8 @@ class ViewContainerRef implements viewEngine.ViewContainerRef {
|
||||||
private _viewRefs: viewEngine.ViewRef[] = [];
|
private _viewRefs: viewEngine.ViewRef[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _lContainerNode: LContainerNode, private _hostNode: LElementNode|LContainerNode) {}
|
private _lContainerNode: LContainerNode,
|
||||||
|
private _hostNode: LElementNode|LElementContainerNode|LContainerNode) {}
|
||||||
|
|
||||||
get element(): ElementRef {
|
get element(): ElementRef {
|
||||||
const injector = getOrCreateNodeInjectorForNode(this._hostNode);
|
const injector = getOrCreateNodeInjectorForNode(this._hostNode);
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} fro
|
||||||
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
|
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
|
||||||
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
|
||||||
import {LInjector} from './interfaces/injector';
|
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 {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
||||||
import {LQueries} from './interfaces/query';
|
import {LQueries} from './interfaces/query';
|
||||||
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||||
|
@ -419,6 +419,9 @@ export function createLNode(
|
||||||
export function createLNode(
|
export function createLNode(
|
||||||
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
|
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
|
||||||
lProjection: null): LProjectionNode;
|
lProjection: null): LProjectionNode;
|
||||||
|
export function createLNode(
|
||||||
|
index: number, type: TNodeType.ElementContainer, native: RComment, name: null,
|
||||||
|
attrs: TAttributes | null, data: null): LElementContainerNode;
|
||||||
export function createLNode(
|
export function createLNode(
|
||||||
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
|
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
|
||||||
attrs: TAttributes | null, state?: null | LViewData | LContainer): LElementNode<extNode&
|
attrs: TAttributes | null, state?: null | LViewData | LContainer): LElementNode<extNode&
|
||||||
|
@ -695,6 +698,50 @@ export function element(
|
||||||
elementEnd();
|
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.
|
* Create DOM element. The instruction must later be followed by `elementEnd()` call.
|
||||||
*
|
*
|
||||||
|
@ -1086,6 +1133,7 @@ export function hostElement(
|
||||||
export function listener(
|
export function listener(
|
||||||
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
|
eventName: string, listenerFn: (e?: any) => any, useCapture = false): void {
|
||||||
ngDevMode && assertPreviousIsParent();
|
ngDevMode && assertPreviousIsParent();
|
||||||
|
ngDevMode && assertNodeOfPossibleTypes(previousOrParentNode, TNodeType.Element);
|
||||||
const node = previousOrParentNode;
|
const node = previousOrParentNode;
|
||||||
const native = node.native as RElement;
|
const native = node.native as RElement;
|
||||||
ngDevMode && ngDevMode.rendererAddEventListener++;
|
ngDevMode && ngDevMode.rendererAddEventListener++;
|
||||||
|
@ -1779,6 +1827,7 @@ export function container(
|
||||||
const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !;
|
const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !;
|
||||||
const lContainer = createLContainer(currentParent, viewData);
|
const lContainer = createLContainer(currentParent, viewData);
|
||||||
|
|
||||||
|
ngDevMode && ngDevMode.rendererCreateComment++;
|
||||||
const comment = renderer.createComment(ngDevMode ? 'container' : '');
|
const comment = renderer.createComment(ngDevMode ? 'container' : '');
|
||||||
const node =
|
const node =
|
||||||
createLNode(index, TNodeType.Container, comment, tagName || null, attrs || null, lContainer);
|
createLNode(index, TNodeType.Container, comment, tagName || null, attrs || null, lContainer);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {ElementRef} from '../../linker/element_ref';
|
||||||
import {TemplateRef} from '../../linker/template_ref';
|
import {TemplateRef} from '../../linker/template_ref';
|
||||||
import {ViewContainerRef} from '../../linker/view_container_ref';
|
import {ViewContainerRef} from '../../linker/view_container_ref';
|
||||||
|
|
||||||
import {LContainerNode, LElementNode} from './node';
|
import {LContainerNode, LElementContainerNode, LElementNode} from './node';
|
||||||
|
|
||||||
export interface LInjector {
|
export interface LInjector {
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,7 @@ export interface LInjector {
|
||||||
* for DI to retrieve a directive from the data array if injector indicates
|
* for DI to retrieve a directive from the data array if injector indicates
|
||||||
* it is there.
|
* it is there.
|
||||||
*/
|
*/
|
||||||
readonly node: LElementNode|LContainerNode;
|
readonly node: LElementNode|LElementContainerNode|LContainerNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The following bloom filter determines whether a directive is available
|
* The following bloom filter determines whether a directive is available
|
||||||
|
|
|
@ -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.
|
* on how to map a particular set of bits in LNode.flags to the node type.
|
||||||
*/
|
*/
|
||||||
export const enum TNodeType {
|
export const enum TNodeType {
|
||||||
Container = 0b00,
|
Container = 0b000,
|
||||||
Projection = 0b01,
|
Projection = 0b001,
|
||||||
View = 0b10,
|
View = 0b010,
|
||||||
Element = 0b11,
|
Element = 0b011,
|
||||||
ViewOrElement = 0b10,
|
ViewOrElement = 0b010,
|
||||||
|
ElementContainer = 0b100,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,6 +119,13 @@ export interface LElementNode extends LNode {
|
||||||
readonly data: LViewData|null;
|
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. */
|
/** LNode representing a #text node. */
|
||||||
export interface LTextNode extends LNode {
|
export interface LTextNode extends LNode {
|
||||||
/** The text node associated with this node. */
|
/** The text node associated with this node. */
|
||||||
|
|
|
@ -29,6 +29,7 @@ declare global {
|
||||||
rendererDestroyNode: number;
|
rendererDestroyNode: number;
|
||||||
rendererMoveNode: number;
|
rendererMoveNode: number;
|
||||||
rendererRemoveNode: number;
|
rendererRemoveNode: number;
|
||||||
|
rendererCreateComment: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +62,7 @@ export function ngDevModeResetPerfCounters() {
|
||||||
rendererDestroyNode: 0,
|
rendererDestroyNode: 0,
|
||||||
rendererMoveNode: 0,
|
rendererMoveNode: 0,
|
||||||
rendererRemoveNode: 0,
|
rendererRemoveNode: 0,
|
||||||
|
rendererCreateComment: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import {assertDefined} from './assert';
|
import {assertDefined} from './assert';
|
||||||
import {callHooks} from './hooks';
|
import {callHooks} from './hooks';
|
||||||
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
|
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 {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
|
||||||
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
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';
|
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. */
|
/** Retrieves the parent LNode of a given node. */
|
||||||
export function getParentLNode(node: LContainerNode | LElementNode | LTextNode | LProjectionNode):
|
export function getParentLNode(
|
||||||
LElementNode|LViewNode;
|
node: LContainerNode | LElementNode | LElementContainerNode | LTextNode |
|
||||||
|
LProjectionNode): LElementNode|LElementContainerNode|LViewNode;
|
||||||
export function getParentLNode(node: LViewNode): LContainerNode|null;
|
export function getParentLNode(node: LViewNode): LContainerNode|null;
|
||||||
export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null;
|
export function getParentLNode(node: LNode): LElementNode|LElementContainerNode|LContainerNode|
|
||||||
export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null {
|
LViewNode|null;
|
||||||
|
export function getParentLNode(node: LNode): LElementNode|LElementContainerNode|LContainerNode|
|
||||||
|
LViewNode|null {
|
||||||
if (node.tNode.index === -1 && node.tNode.type === TNodeType.View) {
|
if (node.tNode.index === -1 && node.tNode.type === TNodeType.View) {
|
||||||
// This is a dynamically created view inside a dynamic container.
|
// 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.
|
// 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 {
|
export function canInsertNativeNode(parent: LNode, currentView: LViewData): boolean {
|
||||||
// We can only insert into a Component or View. Any other type should be an Error.
|
// 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.
|
// Parent is an element.
|
||||||
if (parent.view !== currentView) {
|
if (parent.view !== currentView) {
|
||||||
// If the Parent view is not the same as current view than we are inserting across
|
// 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) ?
|
isProceduralRenderer(renderer) ?
|
||||||
renderer.insertBefore(renderParent !.native, child, beforeNode) :
|
renderer.insertBefore(renderParent !.native, child, beforeNode) :
|
||||||
renderParent !.native.insertBefore(child, beforeNode, true);
|
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 {
|
} else {
|
||||||
isProceduralRenderer(renderer) ? renderer.appendChild(parent.native !as RElement, child) :
|
isProceduralRenderer(renderer) ? renderer.appendChild(parent.native !as RElement, child) :
|
||||||
parent.native !.appendChild(child);
|
parent.native !.appendChild(child);
|
||||||
|
@ -621,8 +631,9 @@ export function removeChild(parent: LNode, child: RNode | null, currentView: LVi
|
||||||
* @param currentView Current LView
|
* @param currentView Current LView
|
||||||
*/
|
*/
|
||||||
export function appendProjectedNode(
|
export function appendProjectedNode(
|
||||||
node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode | LViewNode,
|
node: LElementNode | LElementContainerNode | LTextNode | LContainerNode,
|
||||||
currentView: LViewData, renderParent: LElementNode): void {
|
currentParent: LElementNode | LElementContainerNode | LViewNode, currentView: LViewData,
|
||||||
|
renderParent: LElementNode): void {
|
||||||
appendChild(currentParent, node.native, currentView);
|
appendChild(currentParent, node.native, currentView);
|
||||||
if (node.tNode.type === TNodeType.Container) {
|
if (node.tNode.type === TNodeType.Container) {
|
||||||
// The node we are adding is a container and we are adding it to an element which
|
// The node we are adding is a container and we are adding it to an element which
|
||||||
|
|
|
@ -6,16 +6,17 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {ElementRef} from '@angular/core';
|
||||||
import {RenderFlags} from '@angular/core/src/render3';
|
import {RenderFlags} from '@angular/core/src/render3';
|
||||||
|
|
||||||
import {defineComponent, defineDirective} from '../../src/render3/index';
|
import {AttributeMarker, defineComponent, defineDirective, injectElementRef} from '../../src/render3/index';
|
||||||
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, listener, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
||||||
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
import {InitialStylingFlags} from '../../src/render3/interfaces/definition';
|
||||||
import {HEADER_OFFSET} from '../../src/render3/interfaces/view';
|
import {HEADER_OFFSET} from '../../src/render3/interfaces/view';
|
||||||
import {sanitizeUrl} from '../../src/sanitization/sanitization';
|
import {sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||||
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
|
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
|
||||||
|
|
||||||
import {ComponentFixture, containerEl, renderToHtml} from './render_util';
|
import {ComponentFixture, TemplateFixture, containerEl, renderToHtml} from './render_util';
|
||||||
|
|
||||||
describe('render3 integration test', () => {
|
describe('render3 integration test', () => {
|
||||||
|
|
||||||
|
@ -418,6 +419,104 @@ describe('render3 integration test', () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ng-container', () => {
|
||||||
|
|
||||||
|
it('should insert as a child of a regular element', () => {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <div>before|<ng-container>Greetings<span></span></ng-container>|after</div>
|
||||||
|
*/
|
||||||
|
function Template() {
|
||||||
|
elementStart(0, 'div');
|
||||||
|
{
|
||||||
|
text(1, 'before|');
|
||||||
|
elementContainerStart(2);
|
||||||
|
{
|
||||||
|
text(3, 'Greetings');
|
||||||
|
element(4, 'span');
|
||||||
|
}
|
||||||
|
elementContainerEnd();
|
||||||
|
text(5, '|after');
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = new TemplateFixture(Template);
|
||||||
|
expect(fixture.html).toEqual('<div>before|Greetings<span></span>|after</div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support directives and inject ElementRef', () => {
|
||||||
|
|
||||||
|
class Directive {
|
||||||
|
constructor(public elRef: ElementRef) {}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: Directive,
|
||||||
|
selectors: [['', 'dir', '']],
|
||||||
|
factory: () => new Directive(injectElementRef()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let directive: Directive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <div><ng-container dir></ng-container></div>
|
||||||
|
*/
|
||||||
|
function Template() {
|
||||||
|
elementStart(0, 'div');
|
||||||
|
{
|
||||||
|
elementContainerStart(1, [AttributeMarker.SelectOnly, 'dir']);
|
||||||
|
elementContainerEnd();
|
||||||
|
directive = loadDirective<Directive>(0);
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = new TemplateFixture(Template, () => {}, [Directive]);
|
||||||
|
expect(fixture.html).toEqual('<div></div>');
|
||||||
|
expect(directive !.elRef.nativeElement.nodeType).toBe(Node.COMMENT_NODE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not set any attributes', () => {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <div><ng-container id="foo"></ng-container></div>
|
||||||
|
*/
|
||||||
|
function Template() {
|
||||||
|
elementStart(0, 'div');
|
||||||
|
{
|
||||||
|
elementContainerStart(1, ['id', 'foo']);
|
||||||
|
elementContainerEnd();
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = new TemplateFixture(Template);
|
||||||
|
expect(fixture.html).toEqual('<div></div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when trying to add event listener', () => {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <div><ng-container (click)="..."></ng-container></div>
|
||||||
|
*/
|
||||||
|
function Template() {
|
||||||
|
elementStart(0, 'div');
|
||||||
|
{
|
||||||
|
elementContainerStart(1);
|
||||||
|
{
|
||||||
|
listener('click', function() {});
|
||||||
|
}
|
||||||
|
elementContainerEnd();
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => { new TemplateFixture(Template); }).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('tree', () => {
|
describe('tree', () => {
|
||||||
interface Tree {
|
interface Tree {
|
||||||
beforeLabel?: string;
|
beforeLabel?: string;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
import {EventEmitter} from '../..';
|
import {EventEmitter} from '../..';
|
||||||
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
|
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
|
||||||
import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectViewContainerRef} from '../../src/render3/index';
|
import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectViewContainerRef} from '../../src/render3/index';
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, registerContentQuery} from '../../src/render3/instructions';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
import {query, queryRefresh} from '../../src/render3/query';
|
import {query, queryRefresh} from '../../src/render3/query';
|
||||||
|
|
||||||
|
@ -364,6 +364,43 @@ describe('query', () => {
|
||||||
expect(qList.first.nativeElement).toEqual(elToQuery);
|
expect(qList.first.nativeElement).toEqual(elToQuery);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should query for <ng-container> and read ElementRef with a native element pointing to comment node',
|
||||||
|
() => {
|
||||||
|
let elToQuery;
|
||||||
|
/**
|
||||||
|
* <ng-container #foo></ng-container>
|
||||||
|
* class Cmpt {
|
||||||
|
* @ViewChildren('foo') query;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent(
|
||||||
|
'cmpt',
|
||||||
|
function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementContainerStart(1, null, ['foo', '']);
|
||||||
|
elToQuery = loadElement(1).native;
|
||||||
|
elementContainerEnd();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[], [],
|
||||||
|
function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
query(0, ['foo'], false, QUERY_READ_ELEMENT_REF);
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
let tmp: any;
|
||||||
|
queryRefresh(tmp = load<QueryList<any>>(0)) &&
|
||||||
|
(ctx.query = tmp as QueryList<any>);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmptInstance = renderComponent(Cmpt);
|
||||||
|
const qList = (cmptInstance.query as QueryList<any>);
|
||||||
|
expect(qList.length).toBe(1);
|
||||||
|
expect(isElementRef(qList.first)).toBeTruthy();
|
||||||
|
expect(qList.first.nativeElement).toEqual(elToQuery);
|
||||||
|
});
|
||||||
|
|
||||||
it('should read ViewContainerRef from element nodes when explicitly asked for', () => {
|
it('should read ViewContainerRef from element nodes when explicitly asked for', () => {
|
||||||
/**
|
/**
|
||||||
* <div #foo></div>
|
* <div #foo></div>
|
||||||
|
|
|
@ -222,7 +222,8 @@ export function toHtml<T>(componentOrElement: T | RElement): string {
|
||||||
.replace(/^<div fixture="mark">/, '')
|
.replace(/^<div fixture="mark">/, '')
|
||||||
.replace(/<\/div>$/, '')
|
.replace(/<\/div>$/, '')
|
||||||
.replace(' style=""', '')
|
.replace(' style=""', '')
|
||||||
.replace(/<!--container-->/g, '');
|
.replace(/<!--container-->/g, '')
|
||||||
|
.replace(/<!--ng-container-->/g, '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue