How to debug Angular with Chrome DevTools
Debugging Angular applications with Chrome DevTools provides powerful capabilities for inspecting code, setting breakpoints, and analyzing runtime behavior. With over 12 years of Angular development experience since 2014 and as the creator of CoreUI, I’ve debugged complex issues in production Angular applications. Chrome DevTools offers source maps support, allowing you to debug TypeScript code directly in the browser with full debugging capabilities. This approach enables efficient troubleshooting of component logic, services, and application flow.
Use Chrome DevTools with source maps to debug Angular TypeScript code directly in the browser with breakpoints and inspection.
Enable source maps in Angular:
Source maps are enabled by default in development mode. Verify in angular.json:
{
"projects": {
"your-app": {
"architect": {
"build": {
"configurations": {
"development": {
"sourceMap": true,
"optimization": false
}
}
}
}
}
}
}
Setting breakpoints:
// app.component.ts
import { Component, OnInit } from '@angular/core'
import { UserService } from './services/user.service'
@Component({
selector: 'app-root',
template: `
<div>
<h1>Users</h1>
<button (click)="loadUsers()">Load Users</button>
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
</div>
`
})
export class AppComponent implements OnInit {
users: any[] = []
constructor(private userService: UserService) {
// Breakpoint 1: Check constructor execution
console.log('AppComponent constructor')
}
ngOnInit() {
// Breakpoint 2: Check initialization
console.log('ngOnInit called')
this.loadUsers()
}
loadUsers() {
// Breakpoint 3: Debug user loading
console.log('Loading users...')
this.userService.getUsers().subscribe({
next: (data) => {
// Breakpoint 4: Inspect received data
console.log('Users loaded:', data)
this.users = data
},
error: (error) => {
// Breakpoint 5: Debug errors
console.error('Error loading users:', error)
}
})
}
}
Debug in Chrome DevTools:
- Open Chrome DevTools (F12)
- Go to “Sources” tab
- Press Ctrl+P (Cmd+P on Mac)
- Type filename:
app.component.ts - Click on line numbers to set breakpoints
- Interact with app to trigger breakpoints
- Use debugging controls: Step Over (F10), Step Into (F11), Step Out (Shift+F11)
Debug services:
// user.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
import { tap, catchError } from 'rxjs/operators'
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = '/api/users'
constructor(private http: HttpClient) {
console.log('UserService initialized')
}
getUsers(): Observable<any[]> {
console.log('Fetching users from:', this.apiUrl)
return this.http.get<any[]>(this.apiUrl).pipe(
tap(users => {
// Set breakpoint here to inspect response
console.log('Response received:', users)
console.log('User count:', users.length)
}),
catchError(error => {
// Set breakpoint to debug errors
console.error('HTTP Error:', error)
throw error
})
)
}
getUserById(id: number): Observable<any> {
const url = `${this.apiUrl}/${id}`
console.log('Fetching user:', url)
return this.http.get<any>(url).pipe(
tap(user => console.log('User details:', user))
)
}
}
Using debugger statement:
export class DataComponent implements OnInit {
data: any
ngOnInit() {
this.loadData()
}
loadData() {
this.dataService.getData().subscribe(data => {
// Pause execution here
debugger
// Inspect variables in DevTools
console.log('Data received:', data)
this.data = this.processData(data)
})
}
processData(data: any) {
// Pause before processing
debugger
return data.map(item => ({
...item,
processed: true
}))
}
}
Debugging change detection:
import { Component, ChangeDetectorRef, NgZone } from '@angular/core'
@Component({
selector: 'app-performance',
template: '<div>{{ counter }}</div>'
})
export class PerformanceComponent {
counter = 0
constructor(
private cdr: ChangeDetectorRef,
private zone: NgZone
) {
// Debug zone
this.zone.onStable.subscribe(() => {
console.log('Zone stable')
})
this.zone.onUnstable.subscribe(() => {
console.log('Zone unstable')
})
}
increment() {
console.log('Before increment:', this.counter)
this.counter++
console.log('After increment:', this.counter)
// Manually trigger change detection
this.cdr.detectChanges()
console.log('Change detection triggered')
}
}
Debugging RxJS streams:
import { Component } from '@angular/core'
import { tap, map, filter } from 'rxjs/operators'
@Component({
selector: 'app-stream',
template: '<div>{{ result }}</div>'
})
export class StreamComponent {
result: any
constructor(private dataService: DataService) {
this.dataService.getStream().pipe(
tap(value => {
// Debug each emission
console.log('Stream emitted:', value)
debugger
}),
filter(value => {
console.log('Filtering:', value)
return value > 5
}),
map(value => {
console.log('Mapping:', value)
return value * 2
}),
tap(value => {
console.log('Final value:', value)
})
).subscribe(result => {
console.log('Subscription received:', result)
this.result = result
})
}
}
Inspecting component state:
// In Chrome DevTools Console, access component instance
// 1. Select component element in Elements tab
// 2. In Console, type:
ng.getComponent($0)
// Access component properties
ng.getComponent($0).users
ng.getComponent($0).loadUsers()
// Trigger change detection
ng.applyChanges($0)
// Get component injector
ng.getInjector($0)
Network tab debugging:
// Enable detailed HTTP logging
import { HttpClient, HttpHeaders } from '@angular/common/http'
export class ApiService {
constructor(private http: HttpClient) {}
getData() {
const headers = new HttpHeaders({
'Content-Type': 'application/json'
})
console.log('Making HTTP request')
console.time('API Request')
return this.http.get('/api/data', { headers }).pipe(
tap(() => {
console.timeEnd('API Request')
})
)
}
}
Performance profiling:
export class PerformanceComponent {
processLargeArray(items: any[]) {
console.time('Processing')
const result = items.map(item => {
// Expensive operation
return this.transform(item)
})
console.timeEnd('Processing')
return result
}
transform(item: any) {
console.count('Transform called')
return { ...item, transformed: true }
}
}
Debugging router:
import { Router, NavigationStart, NavigationEnd } from '@angular/router'
constructor(private router: Router) {
this.router.events.subscribe(event => {
console.log('Router event:', event)
if (event instanceof NavigationStart) {
console.log('Navigation starting to:', event.url)
debugger
}
if (event instanceof NavigationEnd) {
console.log('Navigation completed:', event.url)
}
})
}
Best Practice Note
Chrome DevTools automatically maps compiled JavaScript back to TypeScript source files in development mode. Use breakpoints instead of console.log for complex debugging. The Console API provides $0 to access selected element and ng global for Angular-specific utilities. Use “Pause on exceptions” in DevTools to catch errors immediately. The Performance tab helps identify rendering bottlenecks. Network tab shows HTTP requests with timing details. This is how we debug CoreUI Angular applications—leveraging Chrome DevTools source maps, breakpoints, and Angular-specific debugging utilities for efficient troubleshooting of production issues.



