Skip to content

Commit 91a161a

Browse files
refactor(ivy): simplify logic of projectable nodes insertion (angular#29008)
This commit removes code duplication around projectables nodes insertion. It also simplfy the overall logic by using recursive function calls instead of hand-unrolled stack (we can always optimise this part if needed). PR Close angular#29008
1 parent efa10e3 commit 91a161a

File tree

2 files changed

+39
-57
lines changed

2 files changed

+39
-57
lines changed

packages/core/src/render3/instructions.ts

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {SanitizerFn} from './interfaces/sanitization';
3737
import {StylingContext} from './interfaces/styling';
3838
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from './interfaces/view';
3939
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
40-
import {appendChild, appendProjectedNode, createTextNode, insertView, removeView} from './node_manipulation';
40+
import {appendChild, appendProjectedNodes, createTextNode, insertView, removeView} from './node_manipulation';
4141
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
4242
import {applyOnCreateInstructions} from './node_util';
4343
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state';
@@ -2625,14 +2625,6 @@ export function projectionDef(selectors?: CssSelectorList[], textSelectors?: str
26252625
}
26262626
}
26272627

2628-
/**
2629-
* Stack used to keep track of projection nodes in projection() instruction.
2630-
*
2631-
* This is deliberately created outside of projection() to avoid allocating
2632-
* a new array each time the function is called. Instead the array will be
2633-
* re-used by each invocation. This works because the function is not reentrant.
2634-
*/
2635-
const projectionNodeStack: (LView | TNode)[] = [];
26362628

26372629
/**
26382630
* Inserts previously re-distributed projected nodes. This instruction must be preceded by a call
@@ -2655,52 +2647,7 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?:
26552647
setIsParent(false);
26562648

26572649
// re-distribution of projectable nodes is stored on a component's view level
2658-
const componentView = findComponentView(lView);
2659-
const componentNode = componentView[T_HOST] as TElementNode;
2660-
let nodeToProject = (componentNode.projection as(TNode | null)[])[selectorIndex];
2661-
let projectedView = componentView[PARENT] !as LView;
2662-
ngDevMode && assertLView(projectedView);
2663-
let projectionNodeIndex = -1;
2664-
2665-
if (Array.isArray(nodeToProject)) {
2666-
appendChild(nodeToProject, tProjectionNode, lView);
2667-
} else {
2668-
while (nodeToProject) {
2669-
if (nodeToProject.type === TNodeType.Projection) {
2670-
// This node is re-projected, so we must go up the tree to get its projected nodes.
2671-
const currentComponentView = findComponentView(projectedView);
2672-
const currentComponentHost = currentComponentView[T_HOST] as TElementNode;
2673-
const firstProjectedNode = (currentComponentHost.projection as(
2674-
TNode | null)[])[nodeToProject.projection as number];
2675-
2676-
if (firstProjectedNode) {
2677-
if (Array.isArray(firstProjectedNode)) {
2678-
appendChild(firstProjectedNode, tProjectionNode, lView);
2679-
} else {
2680-
projectionNodeStack[++projectionNodeIndex] = nodeToProject;
2681-
projectionNodeStack[++projectionNodeIndex] = projectedView;
2682-
2683-
nodeToProject = firstProjectedNode;
2684-
projectedView = getLViewParent(currentComponentView) !;
2685-
continue;
2686-
}
2687-
}
2688-
} else {
2689-
// This flag must be set now or we won't know that this node is projected
2690-
// if the nodes are inserted into a container later.
2691-
nodeToProject.flags |= TNodeFlags.isProjected;
2692-
appendProjectedNode(nodeToProject, tProjectionNode, lView, projectedView);
2693-
}
2694-
2695-
// If we are finished with a list of re-projected nodes, we need to get
2696-
// back to the root projection node that was re-projected.
2697-
if (nodeToProject.next === null && projectedView !== componentView[PARENT] !) {
2698-
projectedView = projectionNodeStack[projectionNodeIndex--] as LView;
2699-
nodeToProject = projectionNodeStack[projectionNodeIndex--] as TNode;
2700-
}
2701-
nodeToProject = nodeToProject.next;
2702-
}
2703-
}
2650+
appendProjectedNodes(lView, tProjectionNode, selectorIndex, findComponentView(lView));
27042651
}
27052652

27062653
/**

packages/core/src/render3/node_manipulation.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {attachPatchData} from './context_discovery';
1414
import {LContainer, NATIVE, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
1515
import {ComponentDef} from './interfaces/definition';
1616
import {NodeInjectorFactory} from './interfaces/injector';
17-
import {TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
17+
import {TElementNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
1818
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
1919
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
2020
import {CHILD_HEAD, CLEANUP, FLAGS, HEADER_OFFSET, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
@@ -717,6 +717,41 @@ export function nativeRemoveNode(renderer: Renderer3, rNode: RNode, isHostElemen
717717
}
718718
}
719719

720+
/**
721+
* Appends nodes to a target projection place. Nodes to insert were previously re-distribution and
722+
* stored on a component host level.
723+
* @param lView A LView where nodes are inserted (target VLview)
724+
* @param tProjectionNode A projection node where previously re-distribution should be appended
725+
* (target insertion place)
726+
* @param selectorIndex A bucket from where nodes to project should be taken
727+
* @param componentView A where projectable nodes were initially created (source view)
728+
*/
729+
export function appendProjectedNodes(
730+
lView: LView, tProjectionNode: TProjectionNode, selectorIndex: number,
731+
componentView: LView): void {
732+
const projectedView = componentView[PARENT] !as LView;
733+
const componentNode = componentView[T_HOST] as TElementNode;
734+
let nodeToProject = (componentNode.projection as(TNode | null)[])[selectorIndex];
735+
736+
if (Array.isArray(nodeToProject)) {
737+
appendChild(nodeToProject, tProjectionNode, lView);
738+
} else {
739+
while (nodeToProject) {
740+
if (nodeToProject.type === TNodeType.Projection) {
741+
appendProjectedNodes(
742+
lView, tProjectionNode, (nodeToProject as TProjectionNode).projection,
743+
findComponentView(projectedView));
744+
} else {
745+
// This flag must be set now or we won't know that this node is projected
746+
// if the nodes are inserted into a container later.
747+
nodeToProject.flags |= TNodeFlags.isProjected;
748+
appendProjectedNode(nodeToProject, tProjectionNode, lView, projectedView);
749+
}
750+
nodeToProject = nodeToProject.next;
751+
}
752+
}
753+
}
754+
720755
/**
721756
* Appends a projected node to the DOM, or in the case of a projected container,
722757
* appends the nodes from all of the container's active views to the DOM.
@@ -726,7 +761,7 @@ export function nativeRemoveNode(renderer: Renderer3, rNode: RNode, isHostElemen
726761
* @param currentView Current LView
727762
* @param projectionView Projection view (view above current)
728763
*/
729-
export function appendProjectedNode(
764+
function appendProjectedNode(
730765
projectedTNode: TNode, tProjectionNode: TNode, currentView: LView,
731766
projectionView: LView): void {
732767
const native = getNativeByTNode(projectedTNode, projectionView);

0 commit comments

Comments
 (0)