Skip to content

Commit 1336297

Browse files
JoostKAndrewKushnir
authored andcommitted
perf(common): code size reduction of ngFor directive (#44315)
This commit makes several changes to the implementation of `NgForOf` to reduce its code size in production builds: 1. The tailor-made message for an unsupported differ is fully tree-shaken in production builds, in favor of the exception from the differ factory itself. 2. The private `_perViewChange` method was changed into a free-standing function, to allow its name to be minimized. 3. The need for an intermediate `RecordViewTuple` was avoided by applying the operation in-place, instead of collecting all insertions into a buffer first. This is safe as the `_perViewChange` operation that used to be done on each `RecordViewTuple` is entirely local to the tuple itself. Hence, it is invariant to execution ordering which means that the `_perViewChange` can be executed directly during the `forEachOperation` loop. PR Close #44315
1 parent 99d85cb commit 1336297

6 files changed

Lines changed: 43 additions & 46 deletions

File tree

goldens/size-tracking/aio-payloads.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"master": {
1616
"uncompressed": {
1717
"runtime": 4436,
18-
"main": 459047,
18+
"main": 458504,
1919
"polyfills": 37271,
2020
"styles": 70719,
2121
"light-theme": 77717,

goldens/size-tracking/integration-payloads.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"master": {
44
"uncompressed": {
55
"runtime": 1083,
6-
"main": 138491,
6+
"main": 138071,
77
"polyfills": 36964
88
}
99
}
@@ -52,7 +52,7 @@
5252
"master": {
5353
"uncompressed": {
5454
"runtime": 1063,
55-
"main": 163342,
55+
"main": 162474,
5656
"polyfills": 36975
5757
}
5858
}

packages/common/src/directives/ng_for_of.ts

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,19 @@ export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCh
208208
// React on ngForOf changes only once all inputs have been initialized
209209
const value = this._ngForOf;
210210
if (!this._differ && value) {
211-
try {
211+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
212+
try {
213+
// CAUTION: this logic is duplicated for production mode below, as the try-catch
214+
// is only present in development builds.
215+
this._differ = this._differs.find(value).create(this.ngForTrackBy);
216+
} catch {
217+
throw new Error(`Cannot find a differ supporting object '${value}' of type '${
218+
getTypeName(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
219+
}
220+
} else {
221+
// CAUTION: this logic is duplicated for development mode above, as the try-catch
222+
// is only present in development builds.
212223
this._differ = this._differs.find(value).create(this.ngForTrackBy);
213-
} catch {
214-
throw new Error(`Cannot find a differ supporting object '${value}' of type '${
215-
getTypeName(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
216224
}
217225
}
218226
}
@@ -223,53 +231,41 @@ export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCh
223231
}
224232

225233
private _applyChanges(changes: IterableChanges<T>) {
226-
const insertTuples: RecordViewTuple<T, U>[] = [];
234+
const viewContainer = this._viewContainer;
227235
changes.forEachOperation(
228-
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number|null,
236+
(item: IterableChangeRecord<T>, adjustedPreviousIndex: number|null,
229237
currentIndex: number|null) => {
230238
if (item.previousIndex == null) {
231239
// NgForOf is never "null" or "undefined" here because the differ detected
232240
// that a new item needs to be inserted from the iterable. This implies that
233241
// there is an iterable value for "_ngForOf".
234-
const view = this._viewContainer.createEmbeddedView(
235-
this._template, new NgForOfContext<T, U>(null!, this._ngForOf!, -1, -1),
242+
viewContainer.createEmbeddedView(
243+
this._template, new NgForOfContext<T, U>(item.item, this._ngForOf!, -1, -1),
236244
currentIndex === null ? undefined : currentIndex);
237-
const tuple = new RecordViewTuple<T, U>(item, view);
238-
insertTuples.push(tuple);
239245
} else if (currentIndex == null) {
240-
this._viewContainer.remove(
246+
viewContainer.remove(
241247
adjustedPreviousIndex === null ? undefined : adjustedPreviousIndex);
242248
} else if (adjustedPreviousIndex !== null) {
243-
const view = this._viewContainer.get(adjustedPreviousIndex)!;
244-
this._viewContainer.move(view, currentIndex);
245-
const tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForOfContext<T, U>>>view);
246-
insertTuples.push(tuple);
249+
const view = viewContainer.get(adjustedPreviousIndex)!;
250+
viewContainer.move(view, currentIndex);
251+
applyViewChange(view as EmbeddedViewRef<NgForOfContext<T, U>>, item);
247252
}
248253
});
249254

250-
for (let i = 0; i < insertTuples.length; i++) {
251-
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
252-
}
253-
254-
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
255-
const viewRef = <EmbeddedViewRef<NgForOfContext<T, U>>>this._viewContainer.get(i);
256-
viewRef.context.index = i;
257-
viewRef.context.count = ilen;
258-
viewRef.context.ngForOf = this._ngForOf!;
255+
for (let i = 0, ilen = viewContainer.length; i < ilen; i++) {
256+
const viewRef = <EmbeddedViewRef<NgForOfContext<T, U>>>viewContainer.get(i);
257+
const context = viewRef.context;
258+
context.index = i;
259+
context.count = ilen;
260+
context.ngForOf = this._ngForOf!;
259261
}
260262

261263
changes.forEachIdentityChange((record: any) => {
262-
const viewRef =
263-
<EmbeddedViewRef<NgForOfContext<T, U>>>this._viewContainer.get(record.currentIndex);
264-
viewRef.context.$implicit = record.item;
264+
const viewRef = <EmbeddedViewRef<NgForOfContext<T, U>>>viewContainer.get(record.currentIndex);
265+
applyViewChange(viewRef, record);
265266
});
266267
}
267268

268-
private _perViewChange(
269-
view: EmbeddedViewRef<NgForOfContext<T, U>>, record: IterableChangeRecord<any>) {
270-
view.context.$implicit = record.item;
271-
}
272-
273269
/**
274270
* Asserts the correct type of the context for the template that `NgForOf` will render.
275271
*
@@ -282,8 +278,9 @@ export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCh
282278
}
283279
}
284280

285-
class RecordViewTuple<T, U extends NgIterable<T>> {
286-
constructor(public record: any, public view: EmbeddedViewRef<NgForOfContext<T, U>>) {}
281+
function applyViewChange<T>(
282+
view: EmbeddedViewRef<NgForOfContext<T>>, record: IterableChangeRecord<T>) {
283+
view.context.$implicit = record.item;
287284
}
288285

289286
function getTypeName(type: any): string {

packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,9 +434,6 @@
434434
{
435435
"name": "ReactiveFormsModule"
436436
},
437-
{
438-
"name": "RecordViewTuple"
439-
},
440437
{
441438
"name": "RefCountOperator"
442439
},
@@ -656,6 +653,9 @@
656653
{
657654
"name": "applyView"
658655
},
656+
{
657+
"name": "applyViewChange"
658+
},
659659
{
660660
"name": "attachInjectFlag"
661661
},

packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,6 @@
419419
{
420420
"name": "RadioControlRegistryModule"
421421
},
422-
{
423-
"name": "RecordViewTuple"
424-
},
425422
{
426423
"name": "RefCountOperator"
427424
},
@@ -644,6 +641,9 @@
644641
{
645642
"name": "applyView"
646643
},
644+
{
645+
"name": "applyViewChange"
646+
},
647647
{
648648
"name": "attachInjectFlag"
649649
},

packages/core/test/bundling/todo/bundle.golden_symbols.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,6 @@
101101
{
102102
"name": "R3ViewContainerRef"
103103
},
104-
{
105-
"name": "RecordViewTuple"
106-
},
107104
{
108105
"name": "RendererFactory2"
109106
},
@@ -236,6 +233,9 @@
236233
{
237234
"name": "applyView"
238235
},
236+
{
237+
"name": "applyViewChange"
238+
},
239239
{
240240
"name": "assertTemplate"
241241
},

0 commit comments

Comments
 (0)