AngularAtlased. 22.2

Ch. 11Quality & performance52 / 75

DevTools: look inside your app

Chrome DevTools and Angular DevTools step by step: what each panel shows, with exercises that work on this site.

~0 min read Official docs

Chrome DevTools is built into the browser and works on any site, including published apps. It shows you the DOM, the requests, what is stored in the browser, how much the page consumes and what stays in memory. Angular DevTools is a separate extension that adds an “Angular” tab with the component tree, the profiler, the injectors and the routes.

The steps below use the exact button names from Chrome DevTools, taken from its source code. Each section has an exercise you can do right on this page.

In short
  • On a published site (a production build) class names are minified, window.ng does not exist and Angular DevTools refuses to start. For those you need your own app, started with ng serve.
  • Elements, Network, Application, Performance monitor and heap snapshots work everywhere. Search by tags and native types, not by your class names.
  • A leak shows up as a number that grows with every repetition and never comes down: JS event listeners in Performance monitor, a positive # Delta in Comparison.
  • DevTools itself can keep objects alive: whatever you selected in Elements or printed in the Console. Clear the console before the last snapshot.
Why you see nothing on a published site
  • Class names are minified. A heap snapshot does not show LeakyWidget but e, Ge or dc. HTML tags (<app-leaky-widget>) and the browser’s native types (DOMTimer, EventListener) stay readable, though.
  • The ng.* console utilities only exist in development. On the published site ng is undefined.
  • Angular DevTools does not work on production builds. It shows “We detected an application built with production configuration. Angular DevTools only supports development builds.”
  • The HTTP examples on this site use a backend simulated in the browser. Their requests never reach Network. The lab below sends a real request so you have something to follow.

Open DevTools and find any panel

  • Open DevTools with F12 or Ctrl+Shift+I (Windows, Linux), or Cmd+Option+I (Mac). Or right-click the page → Inspect.
  • Panels are tabs at the top: Elements, Console, Sources, Network, Performance, Memory, Application. The ones that do not fit are under ».
  • The fastest way to anything is the command menu, with Ctrl+Shift+P or Cmd+Shift+P. Type “memory” and choose Show Memory, or “performance monitor” and choose Show Performance monitor.
  • Esc opens and closes the bottom drawer, with the Console. That way you see the console next to any other panel.

Practise on this page

Keep DevTools open next to the page. Each card says what you press here and what you look for there.

LiveFour exercises for Elements, Console, Network and Application

Elements

Click the inspect icon in the DevTools corner (or Ctrl+Shift+C / Cmd+Shift+C) and click the grey text next to the button. Then press +1 a few times: in Elements, the data-clicks attribute flashes on every change.

data-clicks="0"

Console

Press the button, then type atlas into the Console’s Filter box. Click the arrow in front of the object to expand it.

Network

In Network pick the Fetch/XHR filter and press the button. A robots.txt?atlas-lab=1 row appears. Click it and go through Headers, Response and Timing.

Set throttling to Slow 4G and send again: the time goes up, and Timing shows where it goes.

Application

Save some text, then open Application → Storage → Local storage → the site’s address. Double-click the value of the atlas-devtools-lab key, change it, then press Read again.

What is in localStorage now: null

The atlas-progress key is there too: your progress through the lessons, kept only in your browser.

They all work on the published site too: nothing depends on class names.

Elements: what Angular rendered

Elements shows the DOM as it is now, not the HTML the server sent. A few things Angular leaves in it:

  • The root element (<app-root>) carries an ng-version attribute with the exact Angular version. On this site: 22.2.2.
  • The _nghost-ng-c… and _ngcontent-ng-c… attributes come from style encapsulation (Emulated mode). In the Styles pane on the right, selectors look like .btn[_ngcontent-ng-c…].
  • The empty <!----> comments are Angular’s anchors for @if, @for and @defer: the place where it inserts or removes content.
  • The selected element is available in the Console as $0. Attributes and text that change flash briefly in Elements: that is how you see what Angular updates.

Console: the ng utilities (development only)

In your own app started with ng serve, Angular puts a global ng object on the page. Select an element in Elements, then in the Console:

// Elements: select an element; in the Console it is $0 (development only)
ng.getComponent($0)          // the component whose host is $0
ng.getOwningComponent($0)    // the component whose template contains $0
ng.getDirectives($0)         // the directives on $0
ng.getInjector($0)           // the element injector: .get(SomeService)
ng.getListeners($0)          // the listeners Angular added to $0
ng.getRootComponents()       // the root components

const c = ng.getComponent($0);
c.count.set(42);             // a signal is written directly; Angular renders on its own
c.title = 'Test';            // a plain property needs applyChanges
ng.applyChanges(c);

On a production build these functions do not exist. That is not a bug: they are removed on purpose so they do not weigh down the app.

Network: what the app requests

Network only records while it is open. If you open it after the page has loaded, reload the page.

  • The filters at the top (Fetch/XHR, JS, CSS, Img…) and the text box leave only the requests you care about.
  • Click a request: Headers (URL, status, headers), Payload (what you sent), Preview and Response (what came back), Initiator (which code started it), Timing (where the time went).
  • Keep log (in older versions: Preserve log) keeps the list across navigation and reloads. Disable cache skips the cache while DevTools is open.
  • The Throttling menu simulates a slower network: Fast 4G, Slow 4G, 3G or Offline. That way you see skeletons, loading states and what happens without a network.

Application: what the app keeps in the browser

  • Storage → Local storage / Session storage → the site’s address: the keys and values, which you can edit or delete.
  • Cookies, IndexedDB and Cache storage are in the same place. Cache storage is where a Service Worker keeps files for offline use.
  • The Service workers section shows the active worker. Update on reload forces the new version on every reload, and Bypass for network skips it. Both are handy while you work on it.
  • Storage → Clear site data deletes everything the site saved. It is the safest way to test a “first visit”.

Performance monitor: the quick check

Before any snapshots, look at live numbers:

  1. Command menu (Ctrl/Cmd+Shift+P) → “performance monitor” → Show Performance monitor. It opens at the bottom, in the drawer.
  2. Watch JS heap size, DOM nodes and JS event listeners. Click a name to show or hide its chart.
  3. In the lab below, with the fixes unchecked, press Open and close 10 times. JS event listeners grows by 10 and stays there. Press Clean up the zombies: it goes back down. With all fixes checked, it does not grow at all.

Memory: a leak found step by step

The lab from the memory leaks lesson, here, so you have something to measure. Every widget closed with the fixes unchecked leaves behind a timer, a listener and a subscription that keep the component in memory.

LiveWidgets that leak memory
Close on destroy:

No widget on the page.

destroyed, still alive
0
memory retained
0.00 MB
zombie work
0/s
subscriptions
0
timers
0
listeners
0
widgets created
0
Uncheck the fixes, press Open and close 10 times, then follow the steps below.

Heap snapshot and Comparison

  1. Open the Memory panel. Under Select profiling type choose Heap snapshot.
  2. Click the bin icon, Collect garbage, then Take snapshot. Snapshot 1 appears on the left.
  3. On the page, with the fixes unchecked, press Open and close 10 times and wait for it to finish.
  4. Again Collect garbage and Take snapshot: Snapshot 2 appears.
  5. With Snapshot 2 selected, switch the first menu in the top bar from Summary to Comparison. The menu next to it shows what you compare against: Snapshot 1.
  6. Type a name from the table below into Filter by class and look at the # New and # Delta columns.
  7. Press Clean up the zombies, take Snapshot 3 and compare it with Snapshot 2: the same rows now show up under # Deleted.
Filter by class# DeltaWhat it means
app-leaky-widget+10The host elements of the 10 widgets, removed from the page but still held in memory. Visible on the published site too.
DOMTimer+10The 10 setIntervals still running. It is a native browser type, so the name stays readable in production too.
EventListener+10The listeners added to document and never removed.
LeakyWidget+10The component class. Only shows up in development (usually as _LeakyWidget). In production the name is minified, so there you search by tag.

Who holds the object: Retainers

Expand a row and click an instance. The Retainers pane at the bottom shows who keeps it alive: the top rows are the direct references, and expanding them walks up the chain towards something long-lived (Window, a listener, a timer). The Distance column tells you how many steps away window is. The leak is the link your code should have broken on destroy.

More direct: Detached elements

  1. In Memory → Select profiling type choose Detached elements.
  2. Repeat the action on the page, then press Get detached elements.
  3. The list shows elements removed from the page but still referenced from JavaScript. Here: one <app-leaky-widget> for every closed widget.
DevTools keeps objects alive too
An element you selected in Elements, an object you printed with console.log or a result in the Console stay in memory as long as DevTools needs them. If a snapshot still shows objects after you cleaned up, clear the console (the crossed-circle icon, or Ctrl+L, on Mac Cmd+K), select another element and repeat. In Summary, the Objects retained by DevTools Console filter shows exactly these objects.

Performance: what happens over time

  • Record records while you do something on the page. Record and reload reloads the page and records the startup.
  • Check Show memory timeline to see JS heap, documents, nodes and listeners on the same axis. A leak looks like a staircase that goes up and never comes down.
  • In development, Angular can add its own Angular track with components, change detection and hooks. Turn it on with ng.enableProfiling() in the Console, before Record.
// in the Console, in your app started with ng serve:
ng.enableProfiling();

// or in main.ts, to capture the startup too:
import { enableProfiling } from '@angular/core';

enableProfiling();
bootstrapApplication(App, appConfig);

Angular DevTools

The extension sees the app the way Angular sees it: components, inputs, signals, injectors, routes.

  1. Install Angular DevTools from the Chrome Web Store (there is a Firefox version too).
  2. Start your app with ng serve, open DevTools and look for the Angular tab, possibly under ». Extensions do not run on the browser’s “New tab” page.
  3. On a production build, this site included, you will only see the message “We detected an application built with production configuration”. That is expected.
TabWhat you see
ComponentsThe component and directive tree, including @defer blocks. Click a component: inputs, outputs and properties on the right. Right-click a value to edit it.
ProfilerEach change detection cycle as a bar; the taller it is, the longer it took. Start with the circle at the top left (Start recording). The Change detection checkbox shows which components were actually checked.
Injector TreeThe environment and element injector hierarchy and the path a dependency lookup takes (Angular 17+).
Router TreeThe route tree: path, component, guards, resolvers, lazy or not, which one is active.
  • $ng0 in the Console is the component currently selected in Angular DevTools, $ng1 the one before it.
  • The inspect icon in the extension’s corner: move the mouse over the page and see which component is there.
  • Double-clicking a component takes you to its element in Elements; the icon at the top right takes you to its source in Sources.
  • Show Signal Graph, in the header of the properties pane, draws the component’s signals, computeds and effects and who reads whom (Angular 19+). Give them a debugName so they have readable names.

The usual sources of leaks and how to close each one are in Memory leaks. What to do once you have found what is slow: Performance and Change detection.

Common pitfalls

  • Looking for your class name on a production build. It is not there: search by tag or native type, or start the app with ng serve.
  • Comparing snapshots without Collect garbage first: the heap goes up and down on its own, and small differences mean nothing.
  • Repeating the action only once. A leak is a number that grows by the same step on every repetition. Repeat 10 times and look for +10.
  • Opening Network after the page has loaded and thinking nothing was requested. Network only records while it is open: reload the page.

Cheat sheet

F12 · Ctrl+Shift+I · Cmd+Option+I            open DevTools
Ctrl+Shift+C · Cmd+Shift+C                   pick an element on the page
Ctrl+Shift+P · Cmd+Shift+P                   the command menu
Esc                                          the drawer with the Console

Network       Fetch/XHR · Keep log · Disable cache · Throttling
Application   Storage → Local storage · Service workers · Clear site data
Perf monitor  JS heap size · DOM nodes · JS event listeners
Memory        Heap snapshot → Take snapshot → Comparison → Filter by class
Memory        Detached elements → Get detached elements
Performance   Record · Record and reload · Show memory timeline

development only:
  ng.getComponent($0) · ng.applyChanges(c) · ng.enableProfiling()
  Angular DevTools: Components · Profiler · Injector Tree · Router Tree · $ng0

Ch. 11 · 0 of 4 complete

Finished this lesson?

Mark it and it shows up on your map: in the contents and on the home page.

Marks itself as done once you have read the whole lesson.

  1. Performance — a checklist
  2. Testing with Vitest
  3. DevTools: look inside your app
  4. Angular Aria
See your whole map

All 75 pages · type to filter

  • 01What's new in Angular 21 & 22All the important changes in v21 and v22, briefly, with a link to the page that explains each one.Start
  • 01Your first app, step by stepA reading list built from an empty component to a finished app — signals, @for, inputs and outputs, a service and a form, one step at a time.Start
  • 01Quick decisions“Where do I put this?”, “Signal or Observable?”, “Shared or feature?” — short answers to everyday questions.Start
  • 01Angular interview questions60 real interview questions for junior, mid and senior, with the answer to give, the follow-up and the red flags. Plus a mock interview.Start
  • 02Standalone & bootstrapHow an app starts without NgModule: bootstrapApplication, app.config.ts, imports in the component.Modern fundamentals
  • 02Data binding & eventsInterpolation, property and attribute bindings, events with $event and key filters, and how [( )] is built — what goes into the DOM and what comes back.Modern fundamentals
  • 02Control flow: @if, @for, @switchThe block syntax that replaces *ngIf/*ngFor/ngSwitch, with track, @empty and exhaustive checks.Modern fundamentals
  • 02@let and the new template expressionsLocal variables with @let, arrow functions, spread, regex, instanceof, comments inside tags.Modern fundamentals
  • 02@defer — lazy loading in templatesLoad heavy components only when needed: triggers, prefetch, placeholder, loading, error.Modern fundamentals
  • 02Class & style bindingsWhy [class.x] and [style.x] replaced ngClass and ngStyle (with automatic migration from v21).Modern fundamentals
  • 02Component styles & encapsulation:host, the three encapsulation modes, theming with CSS variables, and why ::ng-deep is a last resort.Modern fundamentals
  • 02Pipes: when and howPure vs impure pipes, custom pipes, and how you choose between a pipe, computed() and a method.Modern fundamentals
  • 03signal() and computed()The reactive primitives: writable state, derived state, equality, untracked and immutability rules.Signals & reactivity
  • 03effect() — when YES, when NOSide effects synchronised with signals: localStorage, logging, imperative APIs. And their traps.Signals & reactivity
  • 03linkedSignal() — editable derived stateA signal that resets when its source changes, but that you can also set manually.Signals & reactivity
  • 03resource, rxResource, httpResourceAsync data as signals: status, value, error, reload, automatic cancellation. Stable since v22.Signals & reactivity
  • 03debounced() — delayed signalsThe “debounce” version of a signal, without RxJS. Perfect for search.Signals & reactivity
  • 03Lists: search, filter, sort, paginateA chain of computed signals that recalculates only what changed, with the filters kept in the URL so a link reopens the same view.Signals & reactivity
  • 03Observables from zeroWhat an Observable is, subscribe and unsubscribe, the operators you use every week, cold vs hot — and when a signal is enough.Signals & reactivity
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — and when RxJS remains the right choice.Signals & reactivity
  • 04input() and output()Inputs as signals (required, transform, alias) and typed outputs. Data down, events up.Components: communication
  • 04model() — two-way bindingBanana-in-a-box [( )] with a single API. When to use it, and when input + output is better.Components: communication
  • 04viewChild & contentChildQueries as signals: viewChild, viewChildren, contentChild, contentChildren, the read option.Components: communication
  • 04Lifecycle & DestroyRefWhich hooks still matter in the signals era, afterNextRender, DestroyRef and typed ngOnChanges.Components: communication
  • 04Content projectionng-content with select, multiple slots, default content and the trap of conditional projection.Components: communication
  • 04ng-template & ngTemplateOutletTemplates as values: render props in Angular, typed context and customising components.Components: communication
  • 05Smart vs DumbContainer vs presentational: who holds the state, who only displays, and how to split them properly.Component design
  • 05Generic components <T>A correctly typed DataTable<T> or Select<T>, with templates for cells.Component design
  • 05Dynamic componentsNgComponentOutlet, createComponent with inputBinding/outputBinding, and a dashboard built from config.Component design
  • 05Directives & host compositionAttribute directives, the host: {} metadata and the Directive Composition API (hostDirectives).Component design
  • 05Custom form controlsFormValueControl (Signal Forms) vs ControlValueAccessor: a star rating, in both variants.Component design
  • 05Change detectionOnPush is the default from v22, zone.js is gone from v21. What triggers rendering and how not to get burned.Component design
  • 06inject() & @Service()The new @Service() decorator from v22, inject() vs the constructor, and the injection context.Dependency Injection
  • 06Providers & the injector hierarchyRoot vs route vs component, useClass/useValue/useFactory/useExisting, and the optional/self/skipSelf modifiers.Dependency Injection
  • 06InjectionToken & provideX()Typed configuration, provideFeature() functions, makeEnvironmentProviders and initializers.Dependency Injection
  • 06injectAsync() — lazy servicesLoad a heavy service only when you need it, with prefetch on idle.Dependency Injection
  • 07Folder structurecore / shared / features, what goes into each, and what a feature looks like inside.Application architecture
  • 07Shared vs featureWhen to move a component into shared and (especially) when NOT to. A concrete checklist.Application architecture
  • 07Interfaces & modelsInline interface or separate file? DTO vs domain model, type vs interface, where to put them.Application architecture
  • 07State management with signalsSimple signal stores, facades, local vs global state, and when you need NgRx.Application architecture
  • 07Style guide & namingThe current official conventions: file names without suffixes, protected, readonly, member order.Application architecture
  • 07Barrels, aliases & boundariesindex.ts, path aliases, a feature’s public API and dependency rules that can be checked automatically.Application architecture
  • 07Errors: @boundary & ErrorHandlerThe new @boundary block from v22.2, the global ErrorHandler, HTTP errors and errors from resource.Application architecture
  • 08Modern routing & lazy loadingloadComponent, child routes, parameters as inputs and parameter inheritance (the new default in v22).Routing
  • 08Guards & resolverscanActivate, canMatch, canDeactivate and resolve as functions, with redirects via UrlTree / RedirectCommand.Routing
  • 09Signal Forms — the basicsForms built on signals: form(), [formField], schema-based validation. Stable since v22.Forms
  • 09Signal Forms — advancedCustom and cross-field validation, async/HTTP with debounce, dynamic lists, disabled/hidden, reusable schemas.Forms
  • 09Typed Reactive FormsTyped FormGroup, nonNullable, FormArray — and how they coexist with Signal Forms.Forms
  • 10HttpClient, interceptors & the API layerHttpClient without setup (v21), Fetch by default (v22), functional interceptors and an API layer with DTO mapping.HTTP & data
  • 11Performance — a checklistWhat really matters: OnPush + signals, track, @defer, lazy routes, images, bundles and hydration.Quality & performance
  • 11Testing with VitestVitest is the default runner from v21. TestBed, components with signals, services, HTTP and harnesses.Quality & performance
  • 11DevTools: look inside your appChrome DevTools and Angular DevTools step by step: what each panel shows, with exercises that work on this site.Quality & performance
  • 11Angular AriaHeadless, accessible components that you style however you like: tabs, accordion, menu, listbox...Quality & performance
  • 12SSR, prerender & render modesWhen to render on the server, when at build time and when only in the browser: RenderMode per route, app.routes.server.ts and the real costs.SSR & hydration
  • 12Hydration: event replay & incrementalHow the browser takes over server HTML without re-rendering it: full hydration, event replay, @defer (hydrate on …) and mismatch errors.SSR & hydration
  • 12Server-compatible codewindow doesn’t exist on the server: PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, the HTTP cache and PendingTasks.SSR & hydration
  • 13Micro frontendsWhen it is worth splitting an app into several deployments and when it is just complexity: Native Federation, Module Federation, iframes, Web Components.Architecture at scale
  • 13Web Components with Angular ElementscreateCustomElement: an Angular component used as an HTML tag anywhere, plus communication between apps through events and BroadcastChannel.Architecture at scale
  • 13Monorepo, libraries & boundariesA workspace with several apps and libraries: ng generate library, Nx, secondary entry points, publishing and dependency rules.Architecture at scale
  • 13Hexagonal architecture (ports & adapters)A domain without Angular, ports as abstract classes and interchangeable adapters: HTTP, localStorage or in-memory, without touching the UI.Architecture at scale
  • 14Signals under the hoodThe reactive graph: producers and consumers, push-pull, why computed is lazy and glitch-free, the diamond problem and when effects run.Advanced reactivity
  • 14Advanced RxJS: higher-order operatorsswitchMap, mergeMap, concatMap and exhaustMap on a live diagram, plus retry with backoff, shareReplay and how to avoid leaks.Advanced reactivity
  • 14NgRx SignalStoreLarge-scale state management on signals: withState, withComputed, withMethods, rxMethod, entities and hooks — and when a simple store is enough.Advanced reactivity
  • 14State machines with signalsNo more impossible states: a typed machine on signals, with transitions, guards and a live state diagram.Advanced reactivity
  • 15WebSockets: real time that survives the networkA typed socket as signals: reconnect with backoff and jitter, heartbeat, an outbox while offline, and SSR safety.Real time & offline
  • 15Optimistic updates & rollbackAn instant UI that stays honest: snapshot and rollback, out-of-order responses, idempotency keys.Real time & offline
  • 15Offline-first: Service Worker & cachingThe Angular Service Worker: caching strategies, update prompts and an outbox for changes made offline.Real time & offline
  • 16Web WorkersHeavy computation without blocking the UI: a real worker in the page, message passing and when it is not worth it.Advanced UI & performance
  • 16Huge lists: virtual scroll100,000 rows without lag: CDK virtual scroll, itemSize, trackBy and the alternatives (pagination, infinite scroll).Advanced UI & performance
  • 16Modern animations & View Transitionsanimate.enter and animate.leave instead of the deprecated @angular/animations, CSS transitions and View Transitions on navigation.Advanced UI & performance
  • 16Memory leaks: finding and fixing themSubscriptions, timers and listeners that outlive their component — how to spot them in DevTools and close them.Advanced UI & performance
  • 17Security: XSS, sanitisation & CSPWhat Angular does automatically against XSS, when bypassSecurityTrust becomes dangerous, plus CSP with nonce, Trusted Types and XSRF.Security & globalisation
  • 17Authentication: tokens & refreshAn interceptor that attaches the token, refreshes once on 401 and replays the queued requests — plus where you keep the token.Security & globalisation
  • 17OAuth 2.1 & OpenID Connect with PKCEThe authorization code flow with PKCE, step by step: verifier and challenge, state and nonce, the ID token, and why a BFF is safer.Security & globalisation
  • 17Internationalisation (i18n)$localize, the i18n attribute, ICU plural/select, a build per language vs runtime translations, and locale-based formatting.Security & globalisation