Skip to content

Commit

Permalink
perf(core): build-in for should update indexes only when views were a…
Browse files Browse the repository at this point in the history
…dded / removed (#52051)

This change adjust the built-in for algorithm of dealing with embedded views
to update the repeater context (and more specifically - its index field)
only when views were added / removed "in the middle of LContainer" (in places
other than the LContainer end).

This skip iteration over the entire LContainer in most cases - and most importantly
in cases where no diff change was detected.
"

PR Close #52051
  • Loading branch information
pkozlowski-opensource authored and atscott committed Oct 11, 2023
1 parent f66faca commit e906942
Showing 1 changed file with 22 additions and 12 deletions.
34 changes: 22 additions & 12 deletions packages/core/src/render3/instructions/control_flow.ts
Expand Up @@ -154,6 +154,12 @@ export function ɵɵrepeaterCreate(

class LiveCollectionLContainerImpl extends
LiveCollection<LView<RepeaterContext<unknown>>, RepeaterContext<unknown>> {
/**
Property indicating if indexes in the repeater context need to be updated following the live
collection changes. Index updates are necessary if and only if views are inserted / removed in
the middle of LContainer. Adds and removals at the end don't require index updates.
*/
private needsIndexUpdate = false;
constructor(
private lContainer: LContainer, private hostLView: LView, private templateTNode: TNode,
private trackByFn: TrackByFunction<unknown>) {
Expand All @@ -171,10 +177,12 @@ class LiveCollectionLContainerImpl extends
}
override attach(index: number, lView: LView<RepeaterContext<unknown>>): void {
const dehydratedView = lView[HYDRATION] as DehydratedContainerView;
this.needsIndexUpdate ||= index !== this.length;
addLViewToLContainer(
this.lContainer, lView, index, shouldAddViewToDom(this.templateTNode, dehydratedView));
}
override detach(index: number): LView<RepeaterContext<unknown>> {
this.needsIndexUpdate ||= index !== this.length - 1;
return detachExistingView<RepeaterContext<unknown>>(this.lContainer, index);
}
override create(index: number, value: unknown): LView<RepeaterContext<unknown>> {
Expand All @@ -192,6 +200,14 @@ class LiveCollectionLContainerImpl extends
override updateValue(index: number, value: unknown): void {
this.at(index)[CONTEXT].$implicit = value;
}

updateIndexes() {
if (this.needsIndexUpdate) {
for (let i = 0; i < this.length; i++) {
this.at(i)[CONTEXT].$index = i;
}
}
}
}

/**
Expand All @@ -213,18 +229,12 @@ export function ɵɵrepeater(
const lContainer = getLContainer(hostLView, HEADER_OFFSET + containerIndex);
const itemTemplateTNode = getExistingTNode(hostTView, containerIndex);

reconcile(
new LiveCollectionLContainerImpl(
lContainer, hostLView, itemTemplateTNode, metadata.trackByFn),
collection, metadata.trackByFn);

// moves in the container might caused context's index to get out of order, re-adjust
// PERF: we could try to book-keep moves and do this index re-adjust as need, at the cost of the
// additional code complexity
for (let i = 0; i < lContainer.length - CONTAINER_HEADER_OFFSET; i++) {
const lView = getExistingLViewFromLContainer<RepeaterContext<unknown>>(lContainer, i);
lView[CONTEXT].$index = i;
}
const liveCollection = new LiveCollectionLContainerImpl(
lContainer, hostLView, itemTemplateTNode, metadata.trackByFn);
reconcile(liveCollection, collection, metadata.trackByFn);

// moves in the container might caused context's index to get out of order, re-adjust if needed
liveCollection.updateIndexes();

// handle empty blocks
if (metadata.hasEmptyBlock) {
Expand Down

0 comments on commit e906942

Please sign in to comment.