Skip to content

Commit

Permalink
refactor(ivy): replace LNode.dynamicLContainerNode with flat LContain…
Browse files Browse the repository at this point in the history
…ers (#26407)

PR Close #26407
  • Loading branch information
kara authored and mhevery committed Oct 12, 2018
1 parent 70cd112 commit 735dfd3
Show file tree
Hide file tree
Showing 14 changed files with 330 additions and 199 deletions.
26 changes: 24 additions & 2 deletions packages/core/src/render3/context_discovery.ts
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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
}
}
51 changes: 25 additions & 26 deletions packages/core/src/render3/i18n.ts
Expand Up @@ -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.
Expand Down Expand Up @@ -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++;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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:
Expand All @@ -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;
}
Expand Down
95 changes: 53 additions & 42 deletions packages/core/src/render3/instructions.ts
Expand Up @@ -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';
Expand All @@ -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';


/**
Expand Down Expand Up @@ -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};
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down Expand Up @@ -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
Expand All @@ -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) :
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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
];
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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];
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 735dfd3

Please sign in to comment.