0 day streak
javascript / expert
Snippet

Diagnosing Angular's Change Detection Cost with a Custom Profiling Directive

Unlike ngOnChanges, which only fires when an @Input reference actually changes, ngDoCheck runs on every single change detection cycle that reaches a component, regardless of whether anything relevant changed — making it a precise, zero-dependency probe for discovering which subtrees are being checked far more often than expected under default (non-OnPush) change detection. Attaching this directive to suspect elements during development reveals hotspots caused by unstable bindings, such as a function call in a template expression that creates a new object reference every cycle, without needing browser devtools' Angular profiler or third-party instrumentation. The visual red outline turns an abstract check-count number into something a developer can literally see flashing on screen while interacting with the app.

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
import { Directive, inject, ElementRef, DoCheck, Input } from '@angular/core';
@Directive({
selector: '[cdProfiler]',
standalone: true,
})
export class CdProfilerDirective implements DoCheck {
@Input('cdProfiler') label = 'unlabeled';
private readonly el = inject(ElementRef<HTMLElement>);
private checkCount = 0;
private lastLogTime = 0;
// DoCheck fires on EVERY change detection pass touching this host,
// even when no binding actually changed -- unlike ngOnChanges.
ngDoCheck(): void {
this.checkCount++;
const now = performance.now();
if (now - this.lastLogTime > 2000) {
console.warn(
`[cdProfiler:${this.label}] ${this.checkCount} checks in ` +
`the last ${((now - this.lastLogTime) / 1000).toFixed(1)}s`
);
this.el.nativeElement.style.outline =
this.checkCount > 50 ? '2px solid red' : '';
this.checkCount = 0;
this.lastLogTime = now;
}
}
}
angular
Breakdown
1
ngDoCheck(): void {
Hooks into the lowest-level lifecycle callback that fires on literally every change detection pass.
2
this.checkCount++;
Tallies checks per host element to quantify how 'hot' this subtree is relative to others.
3
if (now - this.lastLogTime > 2000) {
Throttles reporting to avoid flooding the console, since ngDoCheck can fire dozens of times per second.
4
this.el.nativeElement.style.outline = this.checkCount > 50 ? '2px solid red' : '';
Converts a numeric threshold into an immediate visual signal directly on the profiled element.