0 day streak
javascript / expert
Snippet

Structural Equality Guards for OnPush Change Detection with Array Snapshots

OnPush components normally re-render whenever a new array reference arrives on an @Input, even if the contents are identical — a common source of wasted change detection cycles in list-heavy UIs. This snippet installs a custom setter that runs a structural equality check before accepting the new reference, so a parent that recreates an array with the same logical rows (a frequent pattern with immutable state stores) no longer forces a re-render. Combined with trackBy, this keeps both the component-level CD pass and the DOM reconciliation pass minimal.

snippet.js
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
interface Row { id: string; score: number; }
function shallowRowsEqual(a: readonly Row[], b: readonly Row[]): boolean {
if (a === b) return true;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i].id !== b[i].id || a[i].score !== b[i].score) return false;
}
return true;
}
@Component({
selector: 'app-leaderboard',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div *ngFor="let row of rows; trackBy: trackById">{{ row.id }}: {{ row.score }}</div>`,
})
class LeaderboardComponent {
private _rows: readonly Row[] = [];
@Input() set rows(next: readonly Row[]) {
if (!shallowRowsEqual(this._rows, next)) {
this._rows = next;
}
}
get rows(): readonly Row[] {
return this._rows;
}
trackById(_index: number, row: Row): string {
return row.id;
}
}
angular
Breakdown
1
function shallowRowsEqual(a: readonly Row[], b: readonly Row[]): boolean {
readonly parameters document and enforce that this comparison never mutates either array.
2
if (a === b) return true;
A fast reference-equality short circuit avoids the O(n) loop for the common no-op update case.
3
@Input() set rows(next: readonly Row[]) {
Using a setter instead of a plain field intercepts every incoming binding to apply the equality guard.
4
if (!shallowRowsEqual(this._rows, next)) {
Only reassigning _rows on genuine change ensures OnPush sees a stable reference otherwise, skipping the render.
5
trackById(_index: number, row: Row): string {
trackBy prevents full DOM node teardown even on the rare occasions the array reference does change.