refactor(ivy): replace LNode.dynamicLContainerNode with flat LContainers (#26407)

PR Close #26407
This commit is contained in:
Kara Erickson 2018-10-11 13:13:57 -07:00 committed by Miško Hevery
parent 70cd112872
commit 735dfd3b1a
14 changed files with 330 additions and 199 deletions

View File

@ -8,8 +8,10 @@
import './ng_dev_mode';
import {assertEqual} from './assert';
import {ACTIVE_INDEX, HOST_NATIVE, LContainer} from './interfaces/container';
import {LElementNode, TNode, TNodeFlags} from './interfaces/node';
import {RElement} from './interfaces/renderer';
import {StylingContext, StylingIndex} from './interfaces/styling';
import {CONTEXT, HEADER_OFFSET, LViewData, TVIEW} from './interfaces/view';
/**
@ -390,6 +392,26 @@ function getDirectiveEndIndex(tNode: TNode, startIndex: number): number {
return count ? (startIndex + count) : -1;
}
export function readElementValue(value: LElementNode | any[]): LElementNode {
return (Array.isArray(value) ? (value as any as any[])[0] : value) as LElementNode;
/**
* Takes the value of a slot in `LViewData` and returns the element node.
*
* Normally, element nodes are stored flat, but if the node has styles/classes on it,
* it might be wrapped in a styling context. Or if that node has a directive that injects
* ViewContainerRef, it may be wrapped in an LContainer.
*
* @param value The initial value in `LViewData`
*/
export function readElementValue(value: LElementNode | StylingContext | LContainer): LElementNode {
if (Array.isArray(value)) {
if (typeof value[ACTIVE_INDEX] === 'number') {
// This is an LContainer. It may also have a styling context.
value = value[HOST_NATIVE] as LElementNode | StylingContext;
return Array.isArray(value) ? value[StylingIndex.ElementPosition] ! : value;
} else {
// This is a StylingContext, which stores the element node at 0.
return value[StylingIndex.ElementPosition] as LElementNode;
}
} else {
return value; // Regular LNode is stored here
}
}

View File

@ -8,11 +8,13 @@
import {assertEqual, assertLessThan} from './assert';
import {NO_CHANGE, _getViewData, adjustBlueprintForNewNode, bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, createNodeAtIndex, getRenderer, getTNode, load, loadElement, resetComponentState} from './instructions';
import {RENDER_PARENT} from './interfaces/container';
import {LContainer, NATIVE, RENDER_PARENT} from './interfaces/container';
import {LContainerNode, LNode, TElementNode, TNode, TNodeType} from './interfaces/node';
import {StylingContext} from './interfaces/styling';
import {BINDING_INDEX, HEADER_OFFSET, HOST_NODE, TVIEW} from './interfaces/view';
import {appendChild, createTextNode, removeChild} from './node_manipulation';
import {stringify} from './util';
import {getLNode, isLContainer, stringify} from './util';
/**
* A list of flags to encode the i18n instructions used to translate the template.
@ -245,9 +247,7 @@ function generateMappingInstructions(
return partIndex;
}
// TODO: Remove LNode arg when we remove dynamicContainerNode
function appendI18nNode(
node: LNode, tNode: TNode, parentTNode: TNode, previousTNode: TNode): TNode {
function appendI18nNode(tNode: TNode, parentTNode: TNode, previousTNode: TNode): TNode {
if (ngDevMode) {
ngDevMode.rendererMoveNode++;
}
@ -272,11 +272,13 @@ function appendI18nNode(
}
}
appendChild(node.native, tNode, viewData);
const native = getLNode(tNode, viewData).native;
appendChild(native, tNode, viewData);
// Template containers also have a comment node for the `ViewContainerRef` that should be moved
if (tNode.type === TNodeType.Container && node.dynamicLContainerNode) {
appendChild(node.dynamicLContainerNode.native, tNode, viewData);
const slotValue = viewData[tNode.index];
if (tNode.type !== TNodeType.Container && isLContainer(slotValue)) {
// Nodes that inject ViewContainerRef also have a comment node that should be moved
appendChild(slotValue[NATIVE], tNode, viewData);
}
return tNode;
@ -327,20 +329,16 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]):
const instruction = instructions[i] as number;
switch (instruction & I18nInstructions.InstructionMask) {
case I18nInstructions.Element:
const elementIndex = instruction & I18nInstructions.IndexMask;
const element: LNode = load(elementIndex);
const elementTNode = getTNode(elementIndex);
localPreviousTNode =
appendI18nNode(element, elementTNode, localParentTNode, localPreviousTNode);
const elementTNode = getTNode(instruction & I18nInstructions.IndexMask);
localPreviousTNode = appendI18nNode(elementTNode, localParentTNode, localPreviousTNode);
localParentTNode = elementTNode;
break;
case I18nInstructions.Expression:
case I18nInstructions.TemplateRoot:
case I18nInstructions.Any:
const nodeIndex = instruction & I18nInstructions.IndexMask;
const node: LNode = load(nodeIndex);
localPreviousTNode =
appendI18nNode(node, getTNode(nodeIndex), localParentTNode, localPreviousTNode);
appendI18nNode(getTNode(nodeIndex), localParentTNode, localPreviousTNode);
break;
case I18nInstructions.Text:
if (ngDevMode) {
@ -352,11 +350,9 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]):
// Create text node at the current end of viewData. Must subtract header offset because
// createNodeAtIndex takes a raw index (not adjusted by header offset).
adjustBlueprintForNewNode(viewData);
const lastNodeIndex = viewData.length - 1 - HEADER_OFFSET;
const textTNode =
createNodeAtIndex(lastNodeIndex, TNodeType.Element, textRNode, null, null);
localPreviousTNode = appendI18nNode(
loadElement(lastNodeIndex), textTNode, localParentTNode, localPreviousTNode);
const textTNode = createNodeAtIndex(
viewData.length - 1 - HEADER_OFFSET, TNodeType.Element, textRNode, null, null);
localPreviousTNode = appendI18nNode(textTNode, localParentTNode, localPreviousTNode);
resetComponentState();
break;
case I18nInstructions.CloseNode:
@ -368,15 +364,18 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]):
ngDevMode.rendererRemoveNode++;
}
const removeIndex = instruction & I18nInstructions.IndexMask;
const removedNode: LNode|LContainerNode = load(removeIndex);
const removedNode: LNode|LContainerNode = loadElement(removeIndex);
const removedTNode = getTNode(removeIndex);
removeChild(removedTNode, removedNode.native || null, viewData);
// For template containers we also need to remove their `ViewContainerRef` from the DOM
if (removedTNode.type === TNodeType.Container && removedNode.dynamicLContainerNode) {
removeChild(removedTNode, removedNode.dynamicLContainerNode.native || null, viewData);
const slotValue = load(removeIndex) as LNode | LContainer | StylingContext;
if (isLContainer(slotValue)) {
const lContainer = slotValue as LContainer;
if (removedTNode.type !== TNodeType.Container) {
removeChild(removedTNode, lContainer[NATIVE] || null, viewData);
}
removedTNode.detached = true;
removedNode.dynamicLContainerNode.data[RENDER_PARENT] = null;
lContainer[RENDER_PARENT] = null;
}
break;
}

View File

@ -16,7 +16,7 @@ import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './asse
import {attachPatchData, getLElementFromComponent, readElementValue, readPatchedLViewData} from './context_discovery';
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
import {ACTIVE_INDEX, HOST_NATIVE, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
import {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {INJECTOR_SIZE} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
@ -29,7 +29,7 @@ import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, createTextNode, findComponentView, getHostElementNode, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {assertDataInRangeInternal, getLNode, getRootView, isContentQueryHost, isDifferent, loadElementInternal, loadInternal, stringify} from './util';
import {assertDataInRangeInternal, getLNode, getRootView, isContentQueryHost, isDifferent, isLContainer, loadElementInternal, loadInternal, stringify} from './util';
/**
@ -430,7 +430,7 @@ export function createLViewData<T>(
export function createLNodeObject(
type: TNodeType, native: RText | RElement | RComment | null, state: any): LElementNode&
LTextNode&LViewNode&LContainerNode&LProjectionNode {
return {native: native as any, data: state, dynamicLContainerNode: null};
return {native: native as any, data: state};
}
/**
@ -453,7 +453,7 @@ export function createNodeAtIndex(
lViewData: LViewData): TViewNode;
export function createNodeAtIndex(
index: number, type: TNodeType.Container, native: RComment, name: string | null,
attrs: TAttributes | null, lContainer: LContainer): TContainerNode;
attrs: TAttributes | null, data: null): TContainerNode;
export function createNodeAtIndex(
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
lProjection: null): TProjectionNode;
@ -935,8 +935,10 @@ function cacheMatchingDirectivesForNode(
function generateExpandoBlock(tNode: TNode, matches: CurrentMatchesList | null): void {
const directiveCount = matches ? matches.length / 2 : 0;
const elementIndex = -(tNode.index - HEADER_OFFSET);
(tView.expandoInstructions || (tView.expandoInstructions = [
])).push(elementIndex, directiveCount);
if (directiveCount > 0) {
(tView.expandoInstructions || (tView.expandoInstructions = [
])).push(elementIndex, directiveCount);
}
}
/**
@ -1418,7 +1420,7 @@ export function elementAttribute(
export function elementProperty<T>(
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void {
if (value === NO_CHANGE) return;
const node = loadElement(index) as LElementNode;
const node = loadElement(index) as LElementNode | LContainerNode | LElementContainerNode;
const tNode = getTNode(index);
// if tNode.inputs is undefined, a listener has created outputs, but inputs haven't
// yet been checked
@ -1431,12 +1433,12 @@ export function elementProperty<T>(
let dataValue: PropertyAliasValue|undefined;
if (inputData && (dataValue = inputData[propName])) {
setInputsForProperty(dataValue, value);
markDirtyIfOnPush(node);
} else {
if (tNode.type === TNodeType.Element) markDirtyIfOnPush(node as LElementNode);
} else if (tNode.type === TNodeType.Element) {
// It is assumed that the sanitizer is only added when the compiler determines that the property
// is risky, so sanitization can be done without further checks.
value = sanitizer != null ? (sanitizer(value) as any) : value;
const native = node.native;
const native = node.native as RElement;
ngDevMode && ngDevMode.rendererSetProperty++;
isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) :
(native.setProperty ? native.setProperty(propName, value) :
@ -1639,16 +1641,22 @@ export function elementStyling<T>(
* @param index Index of the style allocation. See: `elementStyling`.
*/
function getStylingContext(index: number): StylingContext {
let stylingContext = load<StylingContext>(index);
if (!Array.isArray(stylingContext)) {
const lElement = stylingContext as any as LElementNode;
const tNode = getTNode(index);
ngDevMode &&
assertDefined(tNode.stylingTemplate, 'getStylingContext() called before elementStyling()');
stylingContext = viewData[index + HEADER_OFFSET] =
allocStylingContext(lElement, tNode.stylingTemplate !);
let slotValue = viewData[index + HEADER_OFFSET];
if (isLContainer(slotValue)) {
const lContainer = slotValue;
slotValue = lContainer[HOST_NATIVE];
if (!Array.isArray(slotValue)) {
return lContainer[HOST_NATIVE] =
allocStylingContext(slotValue, getTNode(index).stylingTemplate !);
}
} else if (!Array.isArray(slotValue)) {
// This is a regular ElementNode
return viewData[index + HEADER_OFFSET] =
allocStylingContext(slotValue, getTNode(index).stylingTemplate !);
}
return stylingContext;
return slotValue as StylingContext;
}
/**
@ -1969,19 +1977,26 @@ function generateInitialInputs(
/**
* Creates a LContainer, either from a container instruction, or for a ViewContainerRef.
*
* @param hostLNode The host node for the LContainer
* @param hostTNode The host TNode for the LContainer
* @param currentView The parent view of the LContainer
* @param native The native comment element
* @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case
* @returns LContainer
*/
export function createLContainer(
currentView: LViewData, isForViewContainerRef?: boolean): LContainer {
hostLNode: LElementNode | LContainerNode | LElementContainerNode,
hostTNode: TElementNode | TContainerNode | TElementContainerNode, currentView: LViewData,
native: RComment, isForViewContainerRef?: boolean): LContainer {
return [
isForViewContainerRef ? null : 0, // active index
currentView, // parent
null, // next
null, // queries
[], // views
null // renderParent, set after node creation
isForViewContainerRef ? -1 : 0, // active index
currentView, // parent
null, // next
null, // queries
hostLNode, // host native
native, // native
[], // views
getRenderParent(hostTNode, currentView) // renderParent
];
}
@ -2042,12 +2057,13 @@ function containerInternal(
viewData[BINDING_INDEX], tView.bindingStartIndex,
'container nodes should be created before any bindings');
const lContainer = createLContainer(viewData);
ngDevMode && ngDevMode.rendererCreateComment++;
const adjustedIndex = index + HEADER_OFFSET;
const comment = renderer.createComment(ngDevMode ? 'container' : '');
const tNode = createNodeAtIndex(index, TNodeType.Container, comment, tagName, attrs, lContainer);
ngDevMode && ngDevMode.rendererCreateComment++;
const tNode = createNodeAtIndex(index, TNodeType.Container, comment, tagName, attrs, null);
const lContainer = viewData[adjustedIndex] =
createLContainer(viewData[adjustedIndex], tNode, viewData, comment);
lContainer[RENDER_PARENT] = getRenderParent(tNode, viewData);
appendChild(comment, tNode, viewData);
// Containers are added to the current view tree instead of their embedded views
@ -2073,8 +2089,8 @@ export function containerRefreshStart(index: number): void {
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
isParent = true;
// Inline containers cannot have style bindings, so we can read the value directly
(viewData[previousOrParentTNode.index] as LContainerNode).data[ACTIVE_INDEX] = 0;
viewData[index + HEADER_OFFSET][ACTIVE_INDEX] = 0;
if (!checkNoChangesMode) {
// We need to execute init hooks here so ngOnInit hooks are called in top level views
@ -2099,8 +2115,7 @@ export function containerRefreshEnd(): void {
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
// Inline containers cannot have style bindings, so we can read the value directly
const lContainer = viewData[previousOrParentTNode.index].data;
const lContainer = viewData[previousOrParentTNode.index];
const nextIndex = lContainer[ACTIVE_INDEX];
// remove extra views at the end of the container
@ -2118,7 +2133,7 @@ function refreshDynamicEmbeddedViews(lViewData: LViewData) {
// Note: current can be an LViewData or an LContainer instance, but here we are only interested
// in LContainer. We can tell it's an LContainer because its length is less than the LViewData
// header.
if (current.length < HEADER_OFFSET && current[ACTIVE_INDEX] === null) {
if (current.length < HEADER_OFFSET && current[ACTIVE_INDEX] === -1) {
const container = current as LContainer;
for (let i = 0; i < container[VIEWS].length; i++) {
const dynamicViewData = container[VIEWS][i];
@ -2175,12 +2190,10 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num
const containerTNode = previousOrParentTNode.type === TNodeType.View ?
previousOrParentTNode.parent ! :
previousOrParentTNode;
// Inline containers cannot have style bindings, so we can read the value directly
const container = viewData[containerTNode.index] as LContainerNode;
const lContainer = viewData[containerTNode.index] as LContainer;
const currentView = viewData;
ngDevMode && assertNodeType(containerTNode, TNodeType.Container);
const lContainer = container.data;
let viewToRender = scanForView(
lContainer, containerTNode as TContainerNode, lContainer[ACTIVE_INDEX] !, viewBlockId);
@ -2201,7 +2214,7 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num
createNodeAtIndex(viewBlockId, TNodeType.View, null, null, null, viewToRender);
enterView(viewToRender, viewToRender[TVIEW].node);
}
if (container) {
if (lContainer) {
if (creationMode) {
// it is a new view, insert it into collection of views for a given container
insertView(viewToRender, lContainer, currentView, lContainer[ACTIVE_INDEX] !, -1);
@ -2409,12 +2422,10 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?:
continue;
}
} else {
const lNode = projectedView[nodeToProject.index] as LTextNode | LElementNode | LContainerNode;
// This flag must be set now or we won't know that this node is projected
// if the nodes are inserted into a container later.
nodeToProject.flags |= TNodeFlags.isProjected;
appendProjectedNode(lNode, nodeToProject, tProjectionNode, viewData, projectedView);
appendProjectedNode(nodeToProject, tProjectionNode, viewData, projectedView);
}
// If we are finished with a list of re-projected nodes, we need to get

View File

@ -6,8 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {LElementNode} from './node';
import {LContainerNode, LElementContainerNode, LElementNode} from './node';
import {LQueries} from './query';
import {RComment} from './renderer';
import {StylingContext} from './styling';
import {LViewData, NEXT, PARENT, QUERIES} from './view';
/**
@ -18,8 +20,10 @@ import {LViewData, NEXT, PARENT, QUERIES} from './view';
export const ACTIVE_INDEX = 0;
// PARENT, NEXT, and QUERIES are indices 1, 2, and 3.
// As we already have these constants in LViewData, we don't need to re-create them.
export const VIEWS = 4;
export const RENDER_PARENT = 5;
export const HOST_NATIVE = 4;
export const NATIVE = 5;
export const VIEWS = 6;
export const RENDER_PARENT = 7;
/**
* The state associated with an LContainerNode.
@ -37,7 +41,7 @@ export interface LContainer extends Array<any> {
* it is set to null to identify this scenario, as indices are "absolute" in that case,
* i.e. provided directly by the user of the ViewContainerRef API.
*/
[ACTIVE_INDEX]: number|null;
[ACTIVE_INDEX]: number;
/**
* Access to the parent view is necessary so we can propagate back
@ -57,6 +61,13 @@ export interface LContainer extends Array<any> {
*/
[QUERIES]: LQueries|null;
/** The host node of this LContainer. */
// TODO: Should contain just the native element once LNode is removed.
[HOST_NATIVE]: LElementNode|LContainerNode|LElementContainerNode|StylingContext;
/** The comment element that serves as an anchor for this LContainer. */
[NATIVE]: RComment;
/**
* A list of the container's currently active child views. Views will be inserted
* here as they are added and spliced from here when they are removed. We need

View File

@ -71,18 +71,12 @@ export interface LNode {
readonly native: RComment|RElement|RText|null;
/**
* If regular LElementNode, LTextNode, and LProjectionNode then `data` will be null.
* If regular LElementNode, LTextNode, LContainerNode, and LProjectionNode then `data` will be
* null.
* If LElementNode with component, then `data` contains LViewData.
* If LViewNode, then `data` contains the LViewData.
* If LContainerNode, then `data` contains LContainer.
*/
readonly data: LViewData|LContainer|null;
/**
* A pointer to an LContainerNode created by directives requesting ViewContainerRef
*/
// TODO(kara): Remove when removing LNodes
dynamicLContainerNode: LContainerNode|null;
readonly data: LViewData|null;
}
@ -107,14 +101,12 @@ export interface LTextNode extends LNode {
/** The text node associated with this node. */
native: RText;
readonly data: null;
dynamicLContainerNode: null;
}
/** Abstract node which contains root nodes of a view. */
export interface LViewNode extends LNode {
readonly native: null;
readonly data: LViewData;
dynamicLContainerNode: null;
}
/** Abstract node container which contains other views. */
@ -127,14 +119,13 @@ export interface LContainerNode extends LNode {
* until the parent view is processed.
*/
native: RComment;
readonly data: LContainer;
readonly data: null;
}
export interface LProjectionNode extends LNode {
readonly native: null;
readonly data: null;
dynamicLContainerNode: null;
}
/**

View File

@ -9,13 +9,14 @@
import {assertDefined} from './assert';
import {attachPatchData, readElementValue} from './context_discovery';
import {callHooks} from './hooks';
import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {HOST_NATIVE, LContainer, NATIVE, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {LContainerNode, LElementContainerNode, LElementNode, LTextNode, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, 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 {StylingIndex} from './interfaces/styling';
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 {getLNode, stringify} from './util';
import {getLNode, isLContainer, stringify} from './util';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
@ -37,17 +38,15 @@ export function getHostElementNode(currentView: LViewData): LElementNode|null {
null;
}
export function getContainerNode(tNode: TNode, embeddedView: LViewData): LContainerNode|null {
export function getLContainer(tNode: TViewNode, embeddedView: LViewData): LContainer|null {
if (tNode.index === -1) {
// 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.
const containerHostIndex = embeddedView[CONTAINER_INDEX];
return containerHostIndex > -1 ?
embeddedView[PARENT] ![containerHostIndex].dynamicLContainerNode :
null;
return containerHostIndex > -1 ? embeddedView[PARENT] ![containerHostIndex] : null;
} else {
// This is a inline view node (e.g. embeddedViewStart)
return getParentLNode(tNode, embeddedView[PARENT] !) as LContainerNode;
return embeddedView[PARENT] ![tNode.parent !.index] as LContainer;
}
}
@ -57,8 +56,8 @@ export function getContainerNode(tNode: TNode, embeddedView: LViewData): LContai
* Might be null if a view is not yet attached to any container.
*/
export function getContainerRenderParent(tViewNode: TViewNode, view: LViewData): LElementNode|null {
const container = getContainerNode(tViewNode, view);
return container ? container.data[RENDER_PARENT] : null;
const container = getLContainer(tViewNode, view);
return container ? container[RENDER_PARENT] : null;
}
const enum WalkTNodeTreeAction {
@ -107,29 +106,24 @@ function walkTNodeTree(
if (tNode.type === TNodeType.Element) {
const elementNode = getLNode(tNode, currentView);
executeNodeAction(action, renderer, parent, elementNode.native !, beforeNode);
if (elementNode.dynamicLContainerNode) {
executeNodeAction(
action, renderer, parent, elementNode.dynamicLContainerNode.native !, beforeNode);
const nodeOrContainer = currentView[tNode.index];
if (isLContainer(nodeOrContainer)) {
// This element has an LContainer, and its comment needs to be handled
executeNodeAction(action, renderer, parent, nodeOrContainer[NATIVE], beforeNode);
}
} else if (tNode.type === TNodeType.Container) {
const lContainerNode: LContainerNode = currentView ![tNode.index] as LContainerNode;
executeNodeAction(action, renderer, parent, lContainerNode.native !, beforeNode);
const childContainerData: LContainer = lContainerNode.dynamicLContainerNode ?
lContainerNode.dynamicLContainerNode.data :
lContainerNode.data;
if (renderParentNode) {
childContainerData[RENDER_PARENT] = renderParentNode;
}
const lContainer = currentView ![tNode.index] as LContainer;
executeNodeAction(action, renderer, parent, lContainer[NATIVE], beforeNode);
if (childContainerData[VIEWS].length) {
currentView = childContainerData[VIEWS][0];
if (renderParentNode) lContainer[RENDER_PARENT] = renderParentNode;
if (lContainer[VIEWS].length) {
currentView = lContainer[VIEWS][0];
nextTNode = currentView[TVIEW].node;
// When the walker enters a container, then the beforeNode has to become the local native
// comment node.
beforeNode = lContainerNode.dynamicLContainerNode ?
lContainerNode.dynamicLContainerNode.native :
lContainerNode.native;
beforeNode = lContainer[NATIVE];
}
} else if (tNode.type === TNodeType.Projection) {
const componentView = findComponentView(currentView !);
@ -174,7 +168,7 @@ function walkTNodeTree(
// When exiting a container, the beforeNode must be restored to the previous value
if (tNode.type === TNodeType.Container) {
currentView = currentView[PARENT] !;
beforeNode = currentView[tNode.index].native;
beforeNode = currentView[tNode.index][NATIVE];
}
if (tNode.type === TNodeType.View && currentView[NEXT]) {
@ -402,11 +396,14 @@ export function removeView(
/** Gets the child of the given LViewData */
export function getLViewChild(viewData: LViewData): LViewData|LContainer|null {
if (viewData[TVIEW].childIndex === -1) return null;
const childIndex = viewData[TVIEW].childIndex;
if (childIndex === -1) return null;
const hostNode: LElementNode|LContainerNode = viewData[viewData[TVIEW].childIndex];
const value: LElementNode|LContainerNode|LContainer = viewData[childIndex];
return hostNode.data ? hostNode.data : (hostNode.dynamicLContainerNode as LContainerNode).data;
// If it's an array, it's an LContainer. Otherwise, it's a component node, so LViewData
// is stored in data.
return Array.isArray(value) ? value : value.data;
}
/**
@ -444,7 +441,7 @@ export function getParentState(state: LViewData | LContainer, rootView: LViewDat
tNode.type === TNodeType.View) {
// if it's an embedded view, the state needs to go up to the container, in case the
// container has a next
return getContainerNode(tNode, state as LViewData) !.data as any;
return getLContainer(tNode as TViewNode, state as LViewData) as LContainer;
} else {
// otherwise, use parent view for containers or component views
return state[PARENT] === rootView ? null : state[PARENT];
@ -457,7 +454,7 @@ export function getParentState(state: LViewData | LContainer, rootView: LViewDat
* @param view The LViewData to clean up
*/
function cleanUpView(viewOrContainer: LViewData | LContainer): void {
if ((viewOrContainer as LViewData)[TVIEW]) {
if ((viewOrContainer as LViewData).length >= HEADER_OFFSET) {
const view = viewOrContainer as LViewData;
removeListeners(view);
executeOnDestroys(view);
@ -552,8 +549,8 @@ function canInsertNativeChildOfElement(tNode: TNode): boolean {
*/
function canInsertNativeChildOfView(viewTNode: TViewNode, view: LViewData): boolean {
// Because we are inserting into a `View` the `View` may be disconnected.
const container = getContainerNode(viewTNode, view) !;
if (container == null || container.data[RENDER_PARENT] == null) {
const container = getLContainer(viewTNode, view) !;
if (container == null || container[RENDER_PARENT] == null) {
// The `View` is not inserted into a `Container` or the parent `Container`
// itself is disconnected. So we have to delay.
return false;
@ -634,12 +631,13 @@ export function appendChild(
const parentTNode: TNode = childTNode.parent || currentView[HOST_NODE] !;
if (parentTNode.type === TNodeType.View) {
const container = getContainerNode(parentTNode, currentView) as LContainerNode;
const renderParent = container.data[RENDER_PARENT];
const views = container.data[VIEWS];
const lContainer = getLContainer(parentTNode as TViewNode, currentView) as LContainer;
const renderParent = lContainer[RENDER_PARENT];
const views = lContainer[VIEWS];
const index = views.indexOf(currentView);
nativeInsertBefore(
renderer, renderParent !.native, childEl, getBeforeNodeForView(index, views, container));
renderer, renderParent !.native, childEl,
getBeforeNodeForView(index, views, lContainer[NATIVE]));
} else if (parentTNode.type === TNodeType.ElementContainer) {
let elementContainer = getHighestElementContainer(childTNode);
let node: LElementNode = getRenderParent(elementContainer, currentView) !;
@ -666,13 +664,13 @@ function getHighestElementContainer(ngContainer: TNode): TNode {
return ngContainer;
}
export function getBeforeNodeForView(index: number, views: LViewData[], container: LContainerNode) {
export function getBeforeNodeForView(index: number, views: LViewData[], containerNative: RComment) {
if (index + 1 < views.length) {
const view = views[index + 1] as LViewData;
const viewTNode = view[HOST_NODE] as TViewNode;
return viewTNode.child ? getLNode(viewTNode.child, view).native : container.native;
return viewTNode.child ? getLNode(viewTNode.child, view).native : containerNative;
} else {
return container.native;
return containerNative;
}
}
@ -700,49 +698,48 @@ export function removeChild(tNode: TNode, child: RNode | null, currentView: LVie
* Appends a projected node to the DOM, or in the case of a projected container,
* appends the nodes from all of the container's active views to the DOM.
*
* @param projectedLNode The node to process
* @param parentNode The last parent element to be processed
* @param tProjectionNode
* @param projectedTNode The TNode to be projected
* @param tProjectionNode The projection (ng-content) TNode
* @param currentView Current LView
* @param projectionView Projection view
* @param projectionView Projection view (view above current)
*/
export function appendProjectedNode(
projectedLNode: LElementNode | LElementContainerNode | LTextNode | LContainerNode,
projectedTNode: TNode, tProjectionNode: TNode, currentView: LViewData,
projectionView: LViewData): void {
appendChild(projectedLNode.native, tProjectionNode, currentView);
const native = getLNode(projectedTNode, projectionView).native;
appendChild(native, tProjectionNode, currentView);
// the projected contents are processed while in the shadow view (which is the currentView)
// therefore we need to extract the view where the host element lives since it's the
// logical container of the content projected views
attachPatchData(projectedLNode.native, projectionView);
attachPatchData(native, projectionView);
const renderParent = getRenderParent(tProjectionNode, currentView);
const nodeOrContainer = projectionView[projectedTNode.index];
if (projectedTNode.type === TNodeType.Container) {
// The node we are adding is a container and we are adding it to an element which
// is not a component (no more re-projection).
// Alternatively a container is projected at the root of a component's template
// and can't be re-projected (as not content of any component).
// Assign the final projection location in those cases.
const lContainer = (projectedLNode as LContainerNode).data;
lContainer[RENDER_PARENT] = renderParent;
const views = lContainer[VIEWS];
nodeOrContainer[RENDER_PARENT] = renderParent;
const views = nodeOrContainer[VIEWS];
for (let i = 0; i < views.length; i++) {
addRemoveViewFromContainer(views[i], true, projectedLNode.native);
addRemoveViewFromContainer(views[i], true, nodeOrContainer[NATIVE]);
}
} else if (projectedTNode.type === TNodeType.ElementContainer) {
let ngContainerChildTNode: TNode|null = projectedTNode.child as TNode;
while (ngContainerChildTNode) {
let ngContainerChild = getLNode(ngContainerChildTNode, projectionView);
appendProjectedNode(
ngContainerChild as LElementNode | LElementContainerNode | LTextNode | LContainerNode,
ngContainerChildTNode, tProjectionNode, currentView, projectionView);
ngContainerChildTNode = ngContainerChildTNode.next;
} else {
if (projectedTNode.type === TNodeType.ElementContainer) {
let ngContainerChildTNode: TNode|null = projectedTNode.child as TNode;
while (ngContainerChildTNode) {
appendProjectedNode(ngContainerChildTNode, tProjectionNode, currentView, projectionView);
ngContainerChildTNode = ngContainerChildTNode.next;
}
}
if (isLContainer(nodeOrContainer)) {
nodeOrContainer[RENDER_PARENT] = renderParent;
appendChild(nodeOrContainer[NATIVE], tProjectionNode, currentView);
}
}
if (projectedLNode.dynamicLContainerNode) {
projectedLNode.dynamicLContainerNode.data[RENDER_PARENT] = renderParent;
appendChild(projectedLNode.dynamicLContainerNode.native, tProjectionNode, currentView);
}
}

View File

@ -10,11 +10,12 @@ import {devModeEqual} from '../change_detection/change_detection_util';
import {assertDefined, assertLessThan} from './assert';
import {readElementValue, readPatchedLViewData} from './context_discovery';
import {LContainerNode, LElementContainerNode, LElementNode, TNode, TNodeFlags} from './interfaces/node';
import {ACTIVE_INDEX, LContainer} from './interfaces/container';
import {LContainerNode, LElementContainerNode, LElementNode, LNode, TNode, TNodeFlags} from './interfaces/node';
import {StylingContext} from './interfaces/styling';
import {CONTEXT, FLAGS, HEADER_OFFSET, LViewData, LViewFlags, PARENT, RootContext, TData, TVIEW} from './interfaces/view';
/**
* Returns whether the values are different from a change detection stand point.
*
@ -103,6 +104,12 @@ export function isContentQueryHost(tNode: TNode): boolean {
export function isComponent(tNode: TNode): boolean {
return (tNode.flags & TNodeFlags.isComponent) === TNodeFlags.isComponent;
}
export function isLContainer(value: LNode | LContainer | StylingContext): boolean {
// Styling contexts are also arrays, but their first index contains an element node
return Array.isArray(value) && typeof value[ACTIVE_INDEX] === 'number';
}
/**
* Retrieve the root view from any component by walking the parent `LViewData` until
* reaching the root `LViewData`.

View File

@ -18,17 +18,17 @@ import {Renderer2} from '../render/api';
import {assertDefined, assertGreaterThan, assertLessThan} from './assert';
import {NodeInjector, getParentInjectorLocation, getParentInjectorView} from './di';
import {_getViewData, addToViewTree, createEmbeddedViewAndNode, createLContainer, createLNodeObject, createTNode, getPreviousOrParentTNode, getRenderer, renderEmbeddedTemplate} from './instructions';
import {LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
import {_getViewData, addToViewTree, createEmbeddedViewAndNode, createLContainer, getPreviousOrParentTNode, getRenderer, renderEmbeddedTemplate} from './instructions';
import {ACTIVE_INDEX, LContainer, NATIVE, RENDER_PARENT, VIEWS} from './interfaces/container';
import {RenderFlags} from './interfaces/definition';
import {InjectorLocationFlags} from './interfaces/injector';
import {LContainerNode, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode} from './interfaces/node';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode} from './interfaces/node';
import {LQueries} from './interfaces/query';
import {RComment, RElement, Renderer3, isProceduralRenderer} from './interfaces/renderer';
import {CONTEXT, HOST_NODE, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {addRemoveViewFromContainer, appendChild, detachView, findComponentView, getBeforeNodeForView, getRenderParent, insertView, removeView} from './node_manipulation';
import {getLNode, isComponent} from './util';
import {getLNode, isComponent, isLContainer} from './util';
import {ViewRef} from './view_ref';
@ -122,13 +122,12 @@ export function createTemplateRef<T>(
};
}
const hostNode = getLNode(hostTNode, hostView);
const hostContainer: LContainer = hostView[hostTNode.index];
ngDevMode && assertNodeType(hostTNode, TNodeType.Container);
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
return new R3TemplateRef(
hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView,
getRenderer(), hostNode.data ![QUERIES], hostTNode.injectorIndex);
getRenderer(), hostContainer[QUERIES], hostTNode.injectorIndex);
}
let R3ViewContainerRef: {
@ -241,8 +240,8 @@ export function createContainerRef(
insertView(lView, this._lContainer, this._hostView, adjustedIdx, this._hostTNode.index);
const container = this._getHostNode().dynamicLContainerNode !;
const beforeNode = getBeforeNodeForView(adjustedIdx, this._lContainer[VIEWS], container);
const beforeNode =
getBeforeNodeForView(adjustedIdx, this._lContainer[VIEWS], this._lContainer[NATIVE]);
addRemoveViewFromContainer(lView, true, beforeNode);
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
@ -283,25 +282,27 @@ export function createContainerRef(
}
return index;
}
private _getHostNode() { return getLNode(this._hostTNode, this._hostView); }
};
}
const hostLNode = getLNode(hostTNode, hostView);
ngDevMode && assertNodeOfPossibleTypes(
hostTNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer);
const lContainer = createLContainer(hostView, true);
const comment = hostView[RENDERER].createComment(ngDevMode ? 'container' : '');
const lContainerNode: LContainerNode =
createLNodeObject(TNodeType.Container, comment, lContainer);
let lContainer: LContainer;
const slotValue = hostView[hostTNode.index];
if (isLContainer(slotValue)) {
// If the host is a container, we don't need to create a new LContainer
lContainer = slotValue;
lContainer[ACTIVE_INDEX] = -1;
} else {
const comment = hostView[RENDERER].createComment(ngDevMode ? 'container' : '');
ngDevMode && ngDevMode.rendererCreateComment++;
hostView[hostTNode.index] = lContainer =
createLContainer(slotValue, hostTNode, hostView, comment, true);
lContainer[RENDER_PARENT] = getRenderParent(hostTNode, hostView);
appendChild(comment, hostTNode, hostView);
hostLNode.dynamicLContainerNode = lContainerNode;
addToViewTree(hostView, hostTNode.index as number, lContainer);
appendChild(comment, hostTNode, hostView);
addToViewTree(hostView, hostTNode.index as number, lContainer);
}
return new R3ViewContainerRef(lContainer, hostTNode, hostView);
}

View File

@ -68,6 +68,9 @@
{
"name": "HEADER_OFFSET"
},
{
"name": "HOST_NATIVE"
},
{
"name": "HOST_NODE"
},
@ -86,6 +89,9 @@
{
"name": "MONKEY_PATCH_KEY_NAME"
},
{
"name": "NATIVE"
},
{
"name": "NEXT"
},
@ -545,9 +551,6 @@
{
"name": "getComponentDef"
},
{
"name": "getContainerNode"
},
{
"name": "getContainerRenderParent"
},
@ -584,6 +587,9 @@
{
"name": "getInjectorIndex"
},
{
"name": "getLContainer"
},
{
"name": "getLElementFromComponent"
},
@ -755,6 +761,9 @@
{
"name": "isJsObject"
},
{
"name": "isLContainer"
},
{
"name": "isListLikeIterable"
},
@ -782,9 +791,6 @@
{
"name": "listener"
},
{
"name": "load"
},
{
"name": "loadElement"
},

View File

@ -38,6 +38,9 @@
{
"name": "HEADER_OFFSET"
},
{
"name": "HOST_NATIVE"
},
{
"name": "HOST_NODE"
},
@ -50,6 +53,9 @@
{
"name": "MONKEY_PATCH_KEY_NAME"
},
{
"name": "NATIVE"
},
{
"name": "NEXT"
},
@ -224,9 +230,6 @@
{
"name": "getComponentDef"
},
{
"name": "getContainerNode"
},
{
"name": "getContainerRenderParent"
},
@ -242,6 +245,9 @@
{
"name": "getInjectorIndex"
},
{
"name": "getLContainer"
},
{
"name": "getLNode"
},

View File

@ -53,6 +53,9 @@
{
"name": "HEADER_OFFSET"
},
{
"name": "HOST_NATIVE"
},
{
"name": "HOST_NODE"
},
@ -71,6 +74,9 @@
{
"name": "MONKEY_PATCH_KEY_NAME"
},
{
"name": "NATIVE"
},
{
"name": "NEXT"
},
@ -596,9 +602,6 @@
{
"name": "getComponentDef"
},
{
"name": "getContainerNode"
},
{
"name": "getContainerRenderParent"
},
@ -629,6 +632,9 @@
{
"name": "getInjectorIndex"
},
{
"name": "getLContainer"
},
{
"name": "getLElementFromComponent"
},
@ -776,6 +782,9 @@
{
"name": "isJsObject"
},
{
"name": "isLContainer"
},
{
"name": "isListLikeIterable"
},
@ -800,9 +809,6 @@
{
"name": "listener"
},
{
"name": "load"
},
{
"name": "loadElement"
},

View File

@ -323,6 +323,9 @@
{
"name": "HOST_ATTR$1"
},
{
"name": "HOST_NATIVE"
},
{
"name": "HOST_NODE"
},
@ -443,6 +446,9 @@
{
"name": "NAMESPACE_URIS"
},
{
"name": "NATIVE"
},
{
"name": "NATIVE_ADD_LISTENER"
},
@ -1604,9 +1610,6 @@
{
"name": "getComponentDef"
},
{
"name": "getContainerNode"
},
{
"name": "getContainerRenderParent"
},
@ -1667,6 +1670,9 @@
{
"name": "getInjectorIndex"
},
{
"name": "getLContainer"
},
{
"name": "getLElementFromComponent"
},
@ -1976,6 +1982,9 @@
{
"name": "isJsObject"
},
{
"name": "isLContainer"
},
{
"name": "isListLikeIterable"
},
@ -2039,9 +2048,6 @@
{
"name": "listener"
},
{
"name": "load"
},
{
"name": "loadElement"
},

View File

@ -13,7 +13,7 @@ import {defineComponent} from '../../src/render3/definition';
import {bloomAdd, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjector, injectAttribute, injectorHasToken} from '../../src/render3/di';
import {PublicFeature, defineDirective, directiveInject, elementProperty, load, templateRefExtractor} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions';
import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLViewData, createTView, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd, loadElement} from '../../src/render3/instructions';
import {isProceduralRenderer} from '../../src/render3/interfaces/renderer';
import {AttributeMarker, LContainerNode, LElementNode, TNodeType} from '../../src/render3/interfaces/node';
@ -24,6 +24,7 @@ import {getRendererFactory2} from './imported_renderer2';
import {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
import {NgIf} from './common_with_def';
import {TNODE} from '../../src/render3/interfaces/injector';
import {LContainer, NATIVE} from '../../src/render3/interfaces/container';
describe('di', () => {
describe('no dependencies', () => {
@ -1136,7 +1137,7 @@ describe('di', () => {
it('should create ElementRef with comment if requesting directive is on <ng-template> node',
() => {
let dir !: Directive;
let commentNode !: LContainerNode;
let lContainer !: LContainer;
class Directive {
value: string;
@ -1156,13 +1157,13 @@ describe('di', () => {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(0, () => {}, 0, 0, null, ['dir', '']);
commentNode = load(0);
lContainer = load(0) as any;
}
}, 1, 0, [Directive]);
const fixture = new ComponentFixture(App);
expect(dir.value).toContain('ElementRef');
expect(dir.elementRef.nativeElement).toEqual(commentNode.native);
expect(dir.elementRef.nativeElement).toEqual(lContainer[NATIVE]);
});
});

View File

@ -9,7 +9,7 @@
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {RendererStyleFlags2, RendererType2} from '../../src/render/api';
import {AttributeMarker, defineComponent, defineDirective} from '../../src/render3/index';
import {AttributeMarker, defineComponent, defineDirective, templateRefExtractor} from '../../src/render3/index';
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, element, elementAttribute, elementClassProp, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, enableBindings, disableBindings, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, projection, projectionDef, reference, text, textBinding, template} from '../../src/render3/instructions';
import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
@ -1349,6 +1349,7 @@ describe('render3 integration test', () => {
describe('elementClass', () => {
it('should support CSS class toggle', () => {
/** <span [class.active]="class"></span> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'span');
@ -1412,6 +1413,72 @@ describe('render3 integration test', () => {
fixture.update();
expect(fixture.html).toEqual('<span class="existing"></span>');
});
it('should apply classes properly when nodes have LContainers', () => {
let structuralComp !: StructuralComp;
class StructuralComp {
tmp !: TemplateRef<any>;
constructor(public vcr: ViewContainerRef) {}
create() { this.vcr.createEmbeddedView(this.tmp); }
static ngComponentDef = defineComponent({
type: StructuralComp,
selectors: [['structural-comp']],
factory: () => structuralComp =
new StructuralComp(directiveInject(ViewContainerRef as any)),
inputs: {tmp: 'tmp'},
consts: 1,
vars: 0,
template: (rf: RenderFlags, ctx: StructuralComp) => {
if (rf & RenderFlags.Create) {
text(0, 'Comp Content');
}
}
});
}
function FooTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
text(0, 'Temp Content');
}
}
/**
* <ng-template #foo>
* Content
* </ng-template>
* <structural-comp [class.active]="class" [tmp]="foo"></structural-comp>
*/
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
template(0, FooTemplate, 1, 0, '', null, ['foo', ''], templateRefExtractor);
elementStart(2, 'structural-comp');
elementStyling(['active']);
elementEnd();
}
if (rf & RenderFlags.Update) {
const foo = reference(1) as any;
elementClassProp(2, 0, ctx.class);
elementStylingApply(2);
elementProperty(2, 'tmp', bind(foo));
}
}, 3, 1, [StructuralComp]);
const fixture = new ComponentFixture(App);
fixture.component.class = true;
fixture.update();
expect(fixture.html)
.toEqual('<structural-comp class="active">Comp Content</structural-comp>');
structuralComp.create();
fixture.update();
expect(fixture.html)
.toEqual('<structural-comp class="active">Comp Content</structural-comp>Temp Content');
});
});
});