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

View File

@ -16,7 +16,7 @@ import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './asse
import {attachPatchData, getLElementFromComponent, readElementValue, readPatchedLViewData} from './context_discovery'; import {attachPatchData, getLElementFromComponent, readElementValue, readPatchedLViewData} from './context_discovery';
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; 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 {ComponentDef, ComponentQuery, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {INJECTOR_SIZE} from './interfaces/injector'; 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'; 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 {appendChild, appendProjectedNode, createTextNode, findComponentView, getHostElementNode, getLViewChild, getRenderParent, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; 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 {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( export function createLNodeObject(
type: TNodeType, native: RText | RElement | RComment | null, state: any): LElementNode& type: TNodeType, native: RText | RElement | RComment | null, state: any): LElementNode&
LTextNode&LViewNode&LContainerNode&LProjectionNode { 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; lViewData: LViewData): TViewNode;
export function createNodeAtIndex( export function createNodeAtIndex(
index: number, type: TNodeType.Container, native: RComment, name: string | null, 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( export function createNodeAtIndex(
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): TProjectionNode; lProjection: null): TProjectionNode;
@ -935,8 +935,10 @@ function cacheMatchingDirectivesForNode(
function generateExpandoBlock(tNode: TNode, matches: CurrentMatchesList | null): void { function generateExpandoBlock(tNode: TNode, matches: CurrentMatchesList | null): void {
const directiveCount = matches ? matches.length / 2 : 0; const directiveCount = matches ? matches.length / 2 : 0;
const elementIndex = -(tNode.index - HEADER_OFFSET); const elementIndex = -(tNode.index - HEADER_OFFSET);
if (directiveCount > 0) {
(tView.expandoInstructions || (tView.expandoInstructions = [ (tView.expandoInstructions || (tView.expandoInstructions = [
])).push(elementIndex, directiveCount); ])).push(elementIndex, directiveCount);
}
} }
/** /**
@ -1418,7 +1420,7 @@ export function elementAttribute(
export function elementProperty<T>( export function elementProperty<T>(
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void { index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void {
if (value === NO_CHANGE) return; if (value === NO_CHANGE) return;
const node = loadElement(index) as LElementNode; const node = loadElement(index) as LElementNode | LContainerNode | LElementContainerNode;
const tNode = getTNode(index); const tNode = getTNode(index);
// if tNode.inputs is undefined, a listener has created outputs, but inputs haven't // if tNode.inputs is undefined, a listener has created outputs, but inputs haven't
// yet been checked // yet been checked
@ -1431,12 +1433,12 @@ export function elementProperty<T>(
let dataValue: PropertyAliasValue|undefined; let dataValue: PropertyAliasValue|undefined;
if (inputData && (dataValue = inputData[propName])) { if (inputData && (dataValue = inputData[propName])) {
setInputsForProperty(dataValue, value); setInputsForProperty(dataValue, value);
markDirtyIfOnPush(node); if (tNode.type === TNodeType.Element) markDirtyIfOnPush(node as LElementNode);
} else { } else if (tNode.type === TNodeType.Element) {
// It is assumed that the sanitizer is only added when the compiler determines that the property // 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. // is risky, so sanitization can be done without further checks.
value = sanitizer != null ? (sanitizer(value) as any) : value; value = sanitizer != null ? (sanitizer(value) as any) : value;
const native = node.native; const native = node.native as RElement;
ngDevMode && ngDevMode.rendererSetProperty++; ngDevMode && ngDevMode.rendererSetProperty++;
isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) : isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) :
(native.setProperty ? native.setProperty(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`. * @param index Index of the style allocation. See: `elementStyling`.
*/ */
function getStylingContext(index: number): StylingContext { function getStylingContext(index: number): StylingContext {
let stylingContext = load<StylingContext>(index); let slotValue = viewData[index + HEADER_OFFSET];
if (!Array.isArray(stylingContext)) {
const lElement = stylingContext as any as LElementNode; if (isLContainer(slotValue)) {
const tNode = getTNode(index); const lContainer = slotValue;
ngDevMode && slotValue = lContainer[HOST_NATIVE];
assertDefined(tNode.stylingTemplate, 'getStylingContext() called before elementStyling()'); if (!Array.isArray(slotValue)) {
stylingContext = viewData[index + HEADER_OFFSET] = return lContainer[HOST_NATIVE] =
allocStylingContext(lElement, tNode.stylingTemplate !); allocStylingContext(slotValue, getTNode(index).stylingTemplate !);
} }
return stylingContext; } else if (!Array.isArray(slotValue)) {
// This is a regular ElementNode
return viewData[index + HEADER_OFFSET] =
allocStylingContext(slotValue, getTNode(index).stylingTemplate !);
}
return slotValue as StylingContext;
} }
/** /**
@ -1969,19 +1977,26 @@ function generateInitialInputs(
/** /**
* Creates a LContainer, either from a container instruction, or for a ViewContainerRef. * 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 currentView The parent view of the LContainer
* @param native The native comment element
* @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case * @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case
* @returns LContainer * @returns LContainer
*/ */
export function createLContainer( export function createLContainer(
currentView: LViewData, isForViewContainerRef?: boolean): LContainer { hostLNode: LElementNode | LContainerNode | LElementContainerNode,
hostTNode: TElementNode | TContainerNode | TElementContainerNode, currentView: LViewData,
native: RComment, isForViewContainerRef?: boolean): LContainer {
return [ return [
isForViewContainerRef ? null : 0, // active index isForViewContainerRef ? -1 : 0, // active index
currentView, // parent currentView, // parent
null, // next null, // next
null, // queries null, // queries
hostLNode, // host native
native, // native
[], // views [], // views
null // renderParent, set after node creation getRenderParent(hostTNode, currentView) // renderParent
]; ];
} }
@ -2042,12 +2057,13 @@ function containerInternal(
viewData[BINDING_INDEX], tView.bindingStartIndex, viewData[BINDING_INDEX], tView.bindingStartIndex,
'container nodes should be created before any bindings'); 'container nodes should be created before any bindings');
const lContainer = createLContainer(viewData); const adjustedIndex = index + HEADER_OFFSET;
ngDevMode && ngDevMode.rendererCreateComment++;
const comment = renderer.createComment(ngDevMode ? 'container' : ''); 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); appendChild(comment, tNode, viewData);
// Containers are added to the current view tree instead of their embedded views // 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); ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
isParent = true; 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) { if (!checkNoChangesMode) {
// We need to execute init hooks here so ngOnInit hooks are called in top level views // 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); ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
// Inline containers cannot have style bindings, so we can read the value directly const lContainer = viewData[previousOrParentTNode.index];
const lContainer = viewData[previousOrParentTNode.index].data;
const nextIndex = lContainer[ACTIVE_INDEX]; const nextIndex = lContainer[ACTIVE_INDEX];
// remove extra views at the end of the container // 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 // 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 // in LContainer. We can tell it's an LContainer because its length is less than the LViewData
// header. // header.
if (current.length < HEADER_OFFSET && current[ACTIVE_INDEX] === null) { if (current.length < HEADER_OFFSET && current[ACTIVE_INDEX] === -1) {
const container = current as LContainer; const container = current as LContainer;
for (let i = 0; i < container[VIEWS].length; i++) { for (let i = 0; i < container[VIEWS].length; i++) {
const dynamicViewData = container[VIEWS][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 ? const containerTNode = previousOrParentTNode.type === TNodeType.View ?
previousOrParentTNode.parent ! : previousOrParentTNode.parent ! :
previousOrParentTNode; previousOrParentTNode;
// Inline containers cannot have style bindings, so we can read the value directly const lContainer = viewData[containerTNode.index] as LContainer;
const container = viewData[containerTNode.index] as LContainerNode;
const currentView = viewData; const currentView = viewData;
ngDevMode && assertNodeType(containerTNode, TNodeType.Container); ngDevMode && assertNodeType(containerTNode, TNodeType.Container);
const lContainer = container.data;
let viewToRender = scanForView( let viewToRender = scanForView(
lContainer, containerTNode as TContainerNode, lContainer[ACTIVE_INDEX] !, viewBlockId); 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); createNodeAtIndex(viewBlockId, TNodeType.View, null, null, null, viewToRender);
enterView(viewToRender, viewToRender[TVIEW].node); enterView(viewToRender, viewToRender[TVIEW].node);
} }
if (container) { if (lContainer) {
if (creationMode) { if (creationMode) {
// it is a new view, insert it into collection of views for a given container // it is a new view, insert it into collection of views for a given container
insertView(viewToRender, lContainer, currentView, lContainer[ACTIVE_INDEX] !, -1); insertView(viewToRender, lContainer, currentView, lContainer[ACTIVE_INDEX] !, -1);
@ -2409,12 +2422,10 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?:
continue; continue;
} }
} else { } 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 // 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. // if the nodes are inserted into a container later.
nodeToProject.flags |= TNodeFlags.isProjected; nodeToProject.flags |= TNodeFlags.isProjected;
appendProjectedNode(nodeToProject, tProjectionNode, viewData, projectedView);
appendProjectedNode(lNode, nodeToProject, tProjectionNode, viewData, projectedView);
} }
// If we are finished with a list of re-projected nodes, we need to get // 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 * 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 {LQueries} from './query';
import {RComment} from './renderer';
import {StylingContext} from './styling';
import {LViewData, NEXT, PARENT, QUERIES} from './view'; import {LViewData, NEXT, PARENT, QUERIES} from './view';
/** /**
@ -18,8 +20,10 @@ import {LViewData, NEXT, PARENT, QUERIES} from './view';
export const ACTIVE_INDEX = 0; export const ACTIVE_INDEX = 0;
// PARENT, NEXT, and QUERIES are indices 1, 2, and 3. // 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. // As we already have these constants in LViewData, we don't need to re-create them.
export const VIEWS = 4; export const HOST_NATIVE = 4;
export const RENDER_PARENT = 5; export const NATIVE = 5;
export const VIEWS = 6;
export const RENDER_PARENT = 7;
/** /**
* The state associated with an LContainerNode. * 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, * 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. * 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 * 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; [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 * 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 * 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; 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 LElementNode with component, then `data` contains LViewData.
* If LViewNode, then `data` contains the LViewData. * If LViewNode, then `data` contains the LViewData.
* If LContainerNode, then `data` contains LContainer.
*/ */
readonly data: LViewData|LContainer|null; readonly data: LViewData|null;
/**
* A pointer to an LContainerNode created by directives requesting ViewContainerRef
*/
// TODO(kara): Remove when removing LNodes
dynamicLContainerNode: LContainerNode|null;
} }
@ -107,14 +101,12 @@ export interface LTextNode extends LNode {
/** The text node associated with this node. */ /** The text node associated with this node. */
native: RText; native: RText;
readonly data: null; readonly data: null;
dynamicLContainerNode: null;
} }
/** Abstract node which contains root nodes of a view. */ /** Abstract node which contains root nodes of a view. */
export interface LViewNode extends LNode { export interface LViewNode extends LNode {
readonly native: null; readonly native: null;
readonly data: LViewData; readonly data: LViewData;
dynamicLContainerNode: null;
} }
/** Abstract node container which contains other views. */ /** Abstract node container which contains other views. */
@ -127,14 +119,13 @@ export interface LContainerNode extends LNode {
* until the parent view is processed. * until the parent view is processed.
*/ */
native: RComment; native: RComment;
readonly data: LContainer; readonly data: null;
} }
export interface LProjectionNode extends LNode { export interface LProjectionNode extends LNode {
readonly native: null; readonly native: null;
readonly data: null; readonly data: null;
dynamicLContainerNode: null;
} }
/** /**

View File

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

View File

@ -10,11 +10,12 @@ import {devModeEqual} from '../change_detection/change_detection_util';
import {assertDefined, assertLessThan} from './assert'; import {assertDefined, assertLessThan} from './assert';
import {readElementValue, readPatchedLViewData} from './context_discovery'; 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'; 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. * 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 { export function isComponent(tNode: TNode): boolean {
return (tNode.flags & TNodeFlags.isComponent) === TNodeFlags.isComponent; 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 * Retrieve the root view from any component by walking the parent `LViewData` until
* reaching the root `LViewData`. * reaching the root `LViewData`.

View File

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

View File

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

View File

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

View File

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

View File

@ -323,6 +323,9 @@
{ {
"name": "HOST_ATTR$1" "name": "HOST_ATTR$1"
}, },
{
"name": "HOST_NATIVE"
},
{ {
"name": "HOST_NODE" "name": "HOST_NODE"
}, },
@ -443,6 +446,9 @@
{ {
"name": "NAMESPACE_URIS" "name": "NAMESPACE_URIS"
}, },
{
"name": "NATIVE"
},
{ {
"name": "NATIVE_ADD_LISTENER" "name": "NATIVE_ADD_LISTENER"
}, },
@ -1604,9 +1610,6 @@
{ {
"name": "getComponentDef" "name": "getComponentDef"
}, },
{
"name": "getContainerNode"
},
{ {
"name": "getContainerRenderParent" "name": "getContainerRenderParent"
}, },
@ -1667,6 +1670,9 @@
{ {
"name": "getInjectorIndex" "name": "getInjectorIndex"
}, },
{
"name": "getLContainer"
},
{ {
"name": "getLElementFromComponent" "name": "getLElementFromComponent"
}, },
@ -1976,6 +1982,9 @@
{ {
"name": "isJsObject" "name": "isJsObject"
}, },
{
"name": "isLContainer"
},
{ {
"name": "isListLikeIterable" "name": "isListLikeIterable"
}, },
@ -2039,9 +2048,6 @@
{ {
"name": "listener" "name": "listener"
}, },
{
"name": "load"
},
{ {
"name": "loadElement" "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 {bloomAdd, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjector, injectAttribute, injectorHasToken} from '../../src/render3/di';
import {PublicFeature, defineDirective, directiveInject, elementProperty, load, templateRefExtractor} from '../../src/render3/index'; 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 {isProceduralRenderer} from '../../src/render3/interfaces/renderer';
import {AttributeMarker, LContainerNode, LElementNode, TNodeType} from '../../src/render3/interfaces/node'; 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 {ComponentFixture, createComponent, createDirective, getDirectiveOnNode, renderComponent, toHtml} from './render_util';
import {NgIf} from './common_with_def'; import {NgIf} from './common_with_def';
import {TNODE} from '../../src/render3/interfaces/injector'; import {TNODE} from '../../src/render3/interfaces/injector';
import {LContainer, NATIVE} from '../../src/render3/interfaces/container';
describe('di', () => { describe('di', () => {
describe('no dependencies', () => { describe('no dependencies', () => {
@ -1136,7 +1137,7 @@ describe('di', () => {
it('should create ElementRef with comment if requesting directive is on <ng-template> node', it('should create ElementRef with comment if requesting directive is on <ng-template> node',
() => { () => {
let dir !: Directive; let dir !: Directive;
let commentNode !: LContainerNode; let lContainer !: LContainer;
class Directive { class Directive {
value: string; value: string;
@ -1156,13 +1157,13 @@ describe('di', () => {
const App = createComponent('app', function(rf: RenderFlags, ctx: any) { const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
template(0, () => {}, 0, 0, null, ['dir', '']); template(0, () => {}, 0, 0, null, ['dir', '']);
commentNode = load(0); lContainer = load(0) as any;
} }
}, 1, 0, [Directive]); }, 1, 0, [Directive]);
const fixture = new ComponentFixture(App); const fixture = new ComponentFixture(App);
expect(dir.value).toContain('ElementRef'); 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 {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {RendererStyleFlags2, RendererType2} from '../../src/render/api'; 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 {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'; import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition';
@ -1349,6 +1349,7 @@ describe('render3 integration test', () => {
describe('elementClass', () => { describe('elementClass', () => {
it('should support CSS class toggle', () => { it('should support CSS class toggle', () => {
/** <span [class.active]="class"></span> */
const App = createComponent('app', function(rf: RenderFlags, ctx: any) { const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'span'); elementStart(0, 'span');
@ -1412,6 +1413,72 @@ describe('render3 integration test', () => {
fixture.update(); fixture.update();
expect(fixture.html).toEqual('<span class="existing"></span>'); 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');
});
}); });
}); });