AngularPerformanceDevToolsProfilingDebugging

Angular DevTools: Visualizing Performance

P
Performance Engineer
Featured Guide 12 min read

Red bars consist of pain.

The Angular DevTools Profiler records every Change Detection cycle.

If you see a long red bar, it means Angular spent too much time checking your templates. Usually, this means you are binding a heavy function like {{ calculateFactorial() }} directly in the HTML.

Deep Dive: Tick vs DetectChanges

ApplicationRef.tick(): Checks the entire application tree (Root to leaves). Happens automatically on events/XHR.
ChangeDetectorRef.detectChanges(): Checks only the current component and its children. Use this for manual fine-grained control.

02. Flame Graphs

The Flame Graph shows your component tree. The wider the bar, the more time that component (and its children) took to render.

AppComponent (15ms)
Header (1ms)
Dashboard (12ms)
WidgetList (8ms)

04. The Senior Engineer's Take

OnPush is Mandatory

If you aren't using ChangeDetectionStrategy.OnPush, you are essentially asking Angular to check your entire app on every click.

The Profiler makes this obvious: Default components flash on every cycle. OnPush components stay idle (grey) until their inputs change.

âš¡ Interactive Playground

import React, { useState } from 'react';

// 📊 Profiler Visualizer

export default function ProfilerDemo() {
    const [recording, setRecording] = useState(false);
    const [frames, setFrames] = useState([]);

    const toggleRecord = () => {
        if (recording) {
            setRecording(false);
        } else {
            setRecording(true);
            setFrames([]);
            // Simulate capture
            let count = 0;
            const interval = setInterval(() => {
                count++;
                setFrames(p => [...p, { id: count, duration: Math.random() * 20, source: 'Mouse Click' }]);
                if (count > 8) clearInterval(interval);
            }, 500);
        }
    };

    return (
        <div className="bg-slate-50 dark:bg-slate-950 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-xl">
             <div className="flex justify-between items-center mb-10">
                <h3 className="text-2xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                    <span className="text-violet-500">📊</span> DevTools Profiler
                </h3>
                <button 
                    onClick={toggleRecord}
                    className={`w-4 h-4 rounded-full border-2 border-red-500 ${recording ? 'bg-red-500 animate-pulse' : 'bg-transparent'}`}
                ></button>
            </div>

            <div className="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-6 h-[300px] flex flex-col">
                <div className="flex items-center gap-4 border-b border-gray-100 dark:border-slate-800 pb-4 mb-4">
                    <div className="text-xs font-bold text-gray-400 uppercase">Cycle Timeline</div>
                </div>

                <div className="flex-1 flex items-end gap-2 overflow-x-auto pb-2">
                    {frames.length === 0 && <div className="text-center w-full text-gray-300 self-center">Press Record to capture cycles</div>}
                    
                    {frames.map((f) => (
                        <div key={f.id} className="group relative flex flex-col items-center">
                            <div 
                                className={`w-8 rounded-t transition-all hover:opacity-80 ${
                                    f.duration > 15 ? 'bg-red-500' : f.duration > 8 ? 'bg-yellow-400' : 'bg-green-400'
                                }`}
                                style={{ height: `${f.duration * 5}px` }}
                            ></div>
                            <div className="text-[10px] text-gray-400 mt-1">{f.duration.toFixed(0)}ms</div>
                            
                            {/* Tooltip */}
                            <div className="absolute bottom-full mb-2 bg-black text-white text-[10px] p-2 rounded opacity-0 group-hover:opacity-100 whitespace-nowrap z-10 pointer-events-none">
                                Source: {f.source}
                            </div>
                        </div>
                    ))}
                </div>
            </div>
        </div>
    );
}