How to debug Angular apps
Debugging Angular applications efficiently requires understanding the available tools and techniques to identify and fix issues quickly. With over 12 years of Angular development experience since 2014 and as the creator of CoreUI, I’ve debugged countless production issues in enterprise applications. Angular provides powerful debugging capabilities through browser DevTools, Angular DevTools extension, and built-in error messages. This approach helps you quickly identify component state issues, change detection problems, and service errors.
Use browser DevTools and Angular DevTools extension to debug Angular applications with breakpoints, component inspection, and performance profiling.
Install Angular DevTools:
Chrome/Edge: Install “Angular DevTools” extension from browser store.
Browser console debugging:
// app.component.ts
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
template: `
<div>
<h1>{{ title }}</h1>
<button (click)="handleClick()">Click me</button>
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
</div>
`
})
export class AppComponent {
title = 'My App'
items = ['Item 1', 'Item 2', 'Item 3']
handleClick() {
// Add console.log for debugging
console.log('Button clicked')
console.log('Current items:', this.items)
// Use debugger statement to pause execution
debugger
this.items.push(`Item ${this.items.length + 1}`)
// Log component state
console.table(this.items)
}
}
Debug component lifecycle:
import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core'
@Component({
selector: 'app-user',
template: '<div>{{ user?.name }}</div>'
})
export class UserComponent implements OnInit, OnChanges, OnDestroy {
@Input() userId: number
user: any
ngOnChanges(changes: SimpleChanges) {
console.log('ngOnChanges:', changes)
console.log('Previous userId:', changes['userId']?.previousValue)
console.log('Current userId:', changes['userId']?.currentValue)
}
ngOnInit() {
console.log('ngOnInit - Component initialized')
console.log('userId:', this.userId)
this.loadUser()
}
ngOnDestroy() {
console.log('ngOnDestroy - Component destroyed')
}
private loadUser() {
console.log('Loading user...')
// Debug API calls
this.userService.getUser(this.userId).subscribe({
next: (data) => {
console.log('User loaded:', data)
this.user = data
},
error: (err) => {
console.error('Error loading user:', err)
}
})
}
}
Debug services:
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { tap, catchError } from 'rxjs/operators'
import { throwError } from 'rxjs'
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private http: HttpClient) {
console.log('UserService initialized')
}
getUsers() {
console.log('Fetching users...')
return this.http.get('/api/users').pipe(
tap(data => console.log('Users received:', data)),
catchError(error => {
console.error('Error fetching users:', error)
return throwError(() => error)
})
)
}
createUser(user: any) {
console.log('Creating user:', user)
return this.http.post('/api/users', user).pipe(
tap(response => console.log('User created:', response))
)
}
}
Using Angular DevTools:
- Open browser DevTools (F12)
- Navigate to “Angular” tab
- Inspect component tree
- View component properties
- Modify values in real-time
- Profile change detection
- View dependency injection tree
Debug change detection:
import { Component, ChangeDetectorRef, ApplicationRef } from '@angular/core'
@Component({
selector: 'app-debug',
template: '<div>{{ counter }}</div>'
})
export class DebugComponent {
counter = 0
constructor(
private cdr: ChangeDetectorRef,
private appRef: ApplicationRef
) {
// Monitor change detection
this.appRef.isStable.subscribe(stable => {
console.log('App stable:', stable)
})
}
increment() {
console.log('Before:', this.counter)
this.counter++
console.log('After:', this.counter)
// Force change detection
this.cdr.detectChanges()
console.log('Change detection triggered')
}
}
Debug routing:
import { Router, NavigationStart, NavigationEnd, NavigationError } from '@angular/router'
constructor(private router: Router) {
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
console.log('Navigation started:', event.url)
}
if (event instanceof NavigationEnd) {
console.log('Navigation ended:', event.url)
}
if (event instanceof NavigationError) {
console.error('Navigation error:', event.error)
}
})
}
Debug templates:
<!-- Use json pipe to inspect objects -->
<pre>{{ user | json }}</pre>
<!-- Check if variables are defined -->
<div>User exists: {{ !!user }}</div>
<div>User ID: {{ user?.id || 'No ID' }}</div>
<!-- Debug expressions -->
<div>{{ debugExpression() }}</div>
Enable production mode debugging:
// main.ts
import { enableProdMode } from '@angular/core'
// Comment out to enable debugging in production build
// enableProdMode()
Best Practice Note
Use console.log strategically—remove before committing. The debugger statement pauses execution in DevTools for step-by-step debugging. Angular DevTools shows component hierarchy, state, and change detection runs. Use RxJS tap operator to debug Observable streams without affecting data flow. Source maps in development mode map compiled code back to TypeScript. This is how we debug CoreUI Angular applications—combining browser DevTools, Angular DevTools, strategic logging, and breakpoints to quickly identify and resolve component, service, and performance issues in production-grade applications.



