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.
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;}}}