Skip to content
Closed
9 changes: 2 additions & 7 deletions packages/compiler/src/render3/view/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
private _bindingCode: o.Statement[] = [];
private _postfixCode: o.Statement[] = [];
private _temporary = temporaryAllocator(this._prefixCode, TEMPORARY_NAME);
private _projectionDefinitionIndex = -1;
private _valueConverter: ValueConverter;
private _unsupported = unsupported;
private _bindingScope: BindingScope;
Expand Down Expand Up @@ -121,8 +120,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver

// Output a `ProjectionDef` instruction when some `<ng-content>` are present
if (hasNgContent) {
this._projectionDefinitionIndex = this.allocateDataSlot();
const parameters: o.Expression[] = [o.literal(this._projectionDefinitionIndex)];
const parameters: o.Expression[] = [];

// Only selectors with a non-default value are generated
if (ngContentSelectors.length > 1) {
Expand Down Expand Up @@ -217,10 +215,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
visitContent(ngContent: t.Content) {
const slot = this.allocateDataSlot();
const selectorIndex = ngContent.selectorIndex;
const parameters: o.Expression[] = [
o.literal(slot),
o.literal(this._projectionDefinitionIndex),
];
const parameters: o.Expression[] = [o.literal(slot)];

const attributeAsList: string[] = [];

Expand Down
22 changes: 11 additions & 11 deletions packages/compiler/test/render3/r3_compiler_compliance_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,9 +871,9 @@ describe('compiler compliance', () => {
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
template: function SimpleComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) {
$r3$.ɵpD(0);
$r3$.ɵE(1, 'div');
$r3$.ɵP(2, 0);
$r3$.ɵpD();
$r3$.ɵE(0, 'div');
$r3$.ɵP(1);
$r3$.ɵe();
}
}
Expand All @@ -891,12 +891,12 @@ describe('compiler compliance', () => {
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
template: function ComplexComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) {
$r3$.ɵpD(0, $c1$, $c2$);
$r3$.ɵE(1, 'div', $c3$);
$r3$.ɵP(2, 0, 1);
$r3$.ɵpD($c1$, $c2$);
$r3$.ɵE(0, 'div', $c3$);
$r3$.ɵP(1, 1);
$r3$.ɵe();
$r3$.ɵE(3, 'div', $c4$);
$r3$.ɵP(4, 0, 2);
$r3$.ɵE(2, 'div', $c4$);
$r3$.ɵP(3, 2);
$r3$.ɵe();
}
}
Expand Down Expand Up @@ -1022,9 +1022,9 @@ describe('compiler compliance', () => {
template: function ContentQueryComponent_Template(
rf: $RenderFlags$, ctx: $ContentQueryComponent$) {
if (rf & 1) {
$r3$.ɵpD(0);
$r3$.ɵE(1, 'div');
$r3$.ɵP(2, 0);
$r3$.ɵpD();
$r3$.ɵE(0, 'div');
$r3$.ɵP(1);
$r3$.ɵe();
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/render3/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {assertEqual, assertLessThan} from './assert';
import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions';
import {RENDER_PARENT} from './interfaces/container';
import {LContainerNode, LElementNode, LNode, TContainerNode, TNodeType} from './interfaces/node';
import {LContainerNode, LElementNode, LNode, TContainerNode, TElementNode, TNodeType} from './interfaces/node';
import {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view';
import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation';
import {stringify} from './util';
Expand Down Expand Up @@ -243,14 +243,17 @@ function appendI18nNode(node: LNode, parentNode: LNode, previousNode: LNode) {
// On first pass, re-organize node tree to put this node in the correct position.
const firstTemplatePass = node.view[TVIEW].firstTemplatePass;
if (firstTemplatePass) {
node.tNode.next = null;
if (previousNode === parentNode && node.tNode !== parentNode.tNode.child) {
node.tNode.next = parentNode.tNode.child;
parentNode.tNode.child = node.tNode;
} else if (previousNode !== parentNode && node.tNode !== previousNode.tNode.next) {
node.tNode.next = previousNode.tNode.next;
previousNode.tNode.next = node.tNode;
} else {
node.tNode.next = null;
}

if (parentNode.view === node.view) node.tNode.parent = parentNode.tNode as TElementNode;
}

// Template containers also have a comment node for the `ViewContainerRef` that should be moved
Expand Down
204 changes: 87 additions & 117 deletions packages/core/src/render3/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotE
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks';
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {LInjector} from './interfaces/injector';
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';

import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation';
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {ComponentDefInternal, ComponentTemplate, ComponentQuery, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyles as renderElementStyles, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp} from './styling';
import {isDifferent, stringify} from './util';
import {ViewRef} from './view_ref';
import {StylingContext, allocStylingContext, createStylingContextTemplate, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp, renderStyles as renderElementStyles} from './styling';


/**
* Directive (D) sets a property on all component instances using this constant as a key and the
Expand Down Expand Up @@ -335,7 +335,6 @@ export function createLNodeObject(
data: state,
queries: queries,
tNode: null !,
pNextOrParent: null,
dynamicLContainerNode: null
};
}
Expand Down Expand Up @@ -363,11 +362,11 @@ export function createLNode(
attrs: TAttributes | null, lContainer: LContainer): LContainerNode;
export function createLNode(
index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null,
lProjection: LProjection): LProjectionNode;
lProjection: null): LProjectionNode;
export function createLNode(
index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null,
attrs: TAttributes | null, state?: null | LViewData | LContainer | LProjection): LElementNode&
LTextNode&LViewNode&LContainerNode&LProjectionNode {
attrs: TAttributes | null, state?: null | LViewData | LContainer): LElementNode&LTextNode&
LViewNode&LContainerNode&LProjectionNode {
const parent = isParent ? previousOrParentNode :
previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode;
// Parents cannot cross component boundaries because components will be used in multiple places,
Expand Down Expand Up @@ -1189,7 +1188,8 @@ export function createTNode(
parent: parent,
dynamicContainerNode: null,
detached: null,
stylingTemplate: null
stylingTemplate: null,
projection: null
};
}

Expand Down Expand Up @@ -1948,137 +1948,107 @@ export function viewAttached(view: LViewData): boolean {
* @param selectors A collection of parsed CSS selectors
* @param rawSelectors A collection of CSS selectors in the raw, un-parsed form
*/
export function projectionDef(
index: number, selectors?: CssSelectorList[], textSelectors?: string[]): void {
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
const distributedNodes = new Array<LNode[]>(noOfNodeBuckets);
for (let i = 0; i < noOfNodeBuckets; i++) {
distributedNodes[i] = [];
}

export function projectionDef(selectors?: CssSelectorList[], textSelectors?: string[]): void {
const componentNode: LElementNode = findComponentHost(viewData);
let componentChild = getChildLNode(componentNode);

while (componentChild !== null) {
// execute selector matching logic if and only if:
// - there are selectors defined
// - a node has a tag name / attributes that can be matched
if (selectors) {
const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !);
distributedNodes[matchedIdx].push(componentChild);
} else {
distributedNodes[0].push(componentChild);
}

componentChild = getNextLNode(componentChild);
}
if (!componentNode.tNode.projection) {
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
const pData: (TNode | null)[] = componentNode.tNode.projection =
new Array(noOfNodeBuckets).fill(null);
const tails: (TNode | null)[] = pData.slice();

let componentChild = componentNode.tNode.child;

ngDevMode && assertDataNext(index + HEADER_OFFSET);
store(index, distributedNodes);
while (componentChild !== null) {
const bucketIndex =
selectors ? matchingSelectorIndex(componentChild, selectors, textSelectors !) : 0;
const nextNode = componentChild.next;

if (tails[bucketIndex]) {
tails[bucketIndex] !.next = componentChild;
} else {
pData[bucketIndex] = componentChild;
componentChild.next = null;
}
tails[bucketIndex] = componentChild;

componentChild = nextNode;
}
}
}

/**
* Updates the linked list of a projection node, by appending another linked list.
* Stack used to keep track of projection nodes in projection() instruction.
*
* @param projectionNode Projection node whose projected nodes linked list has to be updated
* @param appendedFirst First node of the linked list to append.
* @param appendedLast Last node of the linked list to append.
* This is deliberately created outside of projection() to avoid allocating
* a new array each time the function is called. Instead the array will be
* re-used by each invocation. This works because the function is not reentrant.
*/
function addToProjectionList(
projectionNode: LProjectionNode,
appendedFirst: LElementNode | LTextNode | LContainerNode | null,
appendedLast: LElementNode | LTextNode | LContainerNode | null) {
ngDevMode && assertEqual(
!!appendedFirst, !!appendedLast,
'appendedFirst can be null if and only if appendedLast is also null');
if (!appendedLast) {
// nothing to append
return;
}
const projectionNodeData = projectionNode.data;
if (projectionNodeData.tail) {
projectionNodeData.tail.pNextOrParent = appendedFirst;
} else {
projectionNodeData.head = appendedFirst;
}
projectionNodeData.tail = appendedLast;
appendedLast.pNextOrParent = projectionNode;
}
const projectionNodeStack: LProjectionNode[] = [];

/**
* Inserts previously re-distributed projected nodes. This instruction must be preceded by a call
* to the projectionDef instruction.
*
* @param nodeIndex
* @param localIndex - index under which distribution of projected nodes was memorized
* @param selectorIndex:
* - 0 when the selector is `*` (or unspecified as this is the default value),
* - 1 based index of the selector from the {@link projectionDef}
*/
export function projection(
nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
const node = createLNode(
nodeIndex, TNodeType.Projection, null, null, attrs || null, {head: null, tail: null});
export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?: string[]): void {
const node = createLNode(nodeIndex, TNodeType.Projection, null, null, attrs || null, null);

// We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views.
if (node.tNode.projection === null) node.tNode.projection = selectorIndex;

// `<ng-content>` has no content
isParent = false;

// re-distribution of projectable nodes is memorized on a component's view level
const componentNode = findComponentHost(viewData);
const componentLView = componentNode.data as LViewData;
const distributedNodes = loadInternal(localIndex, componentLView) as Array<LNode[]>;
const nodesForSelector = distributedNodes[selectorIndex];

const currentParent = getParentLNode(node);
const canInsert = canInsertNativeNode(currentParent, viewData);
const renderParent = currentParent.tNode.type === TNodeType.View ?
(getParentLNode(currentParent) as LContainerNode).data[RENDER_PARENT] ! :
currentParent as LElementNode;

for (let i = 0; i < nodesForSelector.length; i++) {
const nodeToProject = nodesForSelector[i];
let head = nodeToProject as LTextNode | LElementNode | LContainerNode | null;
let tail = nodeToProject as LTextNode | LElementNode | LContainerNode | null;

if (nodeToProject.tNode.type === TNodeType.Projection) {
const previouslyProjected = (nodeToProject as LProjectionNode).data;
head = previouslyProjected.head;
tail = previouslyProjected.tail;
}

addToProjectionList(node, head, tail);

if (canInsert) {
let currentNode: LNode|null = head;
while (currentNode) {
// re-distribution of projectable nodes is stored on a component's view level
const parent = getParentLNode(node);

if (canInsertNativeNode(parent, viewData)) {
const componentNode = findComponentHost(viewData);
let nodeToProject = (componentNode.tNode.projection as(TNode | null)[])[selectorIndex];
let projectedView = componentNode.view;
let projectionNodeIndex = -1;
let grandparent: LContainerNode;
const renderParent = parent.tNode.type === TNodeType.View ?
(grandparent = getParentLNode(parent) as LContainerNode) &&
grandparent.data[RENDER_PARENT] ! :
parent as LElementNode;

while (nodeToProject) {
if (nodeToProject.type === TNodeType.Projection) {
// This node is re-projected, so we must go up the tree to get its projected nodes.
const currentComponentHost = findComponentHost(projectedView);
const firstProjectedNode = (currentComponentHost.tNode.projection as(
TNode | null)[])[nodeToProject.projection as number];

if (firstProjectedNode) {
projectionNodeStack[++projectionNodeIndex] = projectedView[nodeToProject.index];
nodeToProject = firstProjectedNode;
projectedView = currentComponentHost.view;
continue;
}
} else {
const lNode = projectedView[nodeToProject.index];
lNode.tNode.flags |= TNodeFlags.isProjected;
appendProjectedNode(
currentNode as LTextNode | LElementNode | LContainerNode, currentParent, viewData,
renderParent);
currentNode = currentNode === tail ? null : currentNode.pNextOrParent;
lNode as LTextNode | LElementNode | LContainerNode, parent, viewData, renderParent);
}
}
}
}

/**
* Given a current view, finds the nearest component's host (LElement).
*
* @param lViewData LViewData for which we want a host element node
* @returns The host node
*/
function findComponentHost(lViewData: LViewData): LElementNode {
let viewRootLNode = lViewData[HOST_NODE];

while (viewRootLNode.tNode.type === TNodeType.View) {
ngDevMode && assertDefined(lViewData[PARENT], 'lViewData.parent');
lViewData = lViewData[PARENT] !;
viewRootLNode = lViewData[HOST_NODE];
// If we are finished with a list of re-projected nodes, we need to get
// back to the root projection node that was re-projected.
if (nodeToProject.next === null && projectedView !== componentNode.view) {
// move down into the view of the component we're projecting right now
const lNode = projectionNodeStack[projectionNodeIndex--];
nodeToProject = lNode.tNode;
projectedView = lNode.view;
}
nodeToProject = nodeToProject.next;
}
}

ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element);
ngDevMode && assertDefined(viewRootLNode.data, 'node.data');

return viewRootLNode as LElementNode;
}

/**
Expand Down
Loading