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.