Overview
HTTP requests can fail for many reasons: network errors, server errors, client errors, or timeouts. Angular provides robust error handling mechanisms through RxJS operators and theHttpErrorResponse class.
Error types
Angular distinguishes between two types of HTTP errors:Client-side errors
Errors that occur in the browser:- Network connectivity issues
- DNS resolution failures
- Request cancellation
- CORS errors
if (error.error instanceof ErrorEvent) {
// Client-side error
console.error('Client error:', error.error.message);
}
Server-side errors
Errors returned by the server:- 4xx client errors (400, 401, 403, 404, etc.)
- 5xx server errors (500, 502, 503, etc.)
- Invalid responses
if (error.status !== 0) {
// Server-side error
console.error(`Server error: ${error.status} - ${error.message}`);
}
HttpErrorResponse
TheHttpErrorResponse object contains detailed error information:
import { HttpErrorResponse } from '@angular/common/http';
http.get('/api/users').subscribe({
next: (data) => console.log(data),
error: (error: HttpErrorResponse) => {
console.log('Error status:', error.status); // HTTP status code
console.log('Error message:', error.message); // Error message
console.log('Error body:', error.error); // Response body
console.log('Error headers:', error.headers); // Response headers
console.log('Error url:', error.url); // Request URL
console.log('Error name:', error.name); // Error name
console.log('Error statusText:', error.statusText); // Status text
}
});
Basic error handling
In subscriptions
Handle errors directly in the subscribe method:import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
export class UserService {
private http = inject(HttpClient);
getUsers() {
this.http.get<User[]>('/api/users').subscribe({
next: (users) => {
console.log('Users loaded:', users);
},
error: (error: HttpErrorResponse) => {
if (error.status === 404) {
console.error('Users not found');
} else if (error.status === 500) {
console.error('Server error occurred');
} else {
console.error('An error occurred:', error.message);
}
},
complete: () => {
console.log('Request completed');
}
});
}
}
Using catchError
Handle errors in the Observable pipe:import { catchError, of } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
export class UserService {
private http = inject(HttpClient);
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
catchError((error: HttpErrorResponse) => {
console.error('Error loading users:', error);
// Return empty array as fallback
return of([]);
})
);
}
}
Centralized error handling
Error handler service
Create a service to centralize error handling logic:error-handler.service.ts
import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class ErrorHandlerService {
handleError(error: HttpErrorResponse): Observable<never> {
let errorMessage = 'An unknown error occurred';
if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = `Client Error: ${error.error.message}`;
} else {
// Server-side error
switch (error.status) {
case 400:
errorMessage = 'Bad Request: Please check your input';
break;
case 401:
errorMessage = 'Unauthorized: Please log in';
break;
case 403:
errorMessage = 'Forbidden: You do not have permission';
break;
case 404:
errorMessage = 'Not Found: The requested resource does not exist';
break;
case 500:
errorMessage = 'Internal Server Error: Please try again later';
break;
case 503:
errorMessage = 'Service Unavailable: Server is temporarily down';
break;
default:
errorMessage = `Server Error (${error.status}): ${error.message}`;
}
}
console.error('HTTP Error:', errorMessage, error);
return throwError(() => new Error(errorMessage));
}
}
user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { catchError, Observable } from 'rxjs';
import { ErrorHandlerService } from './error-handler.service';
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private errorHandler = inject(ErrorHandlerService);
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
catchError(this.errorHandler.handleError)
);
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`/api/users/${id}`).pipe(
catchError(this.errorHandler.handleError)
);
}
}
Error interceptor
Handle errors globally with an interceptor:error.interceptor.ts
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { Router } from '@angular/router';
import { ToastService } from '../services/toast.service';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
const router = inject(Router);
const toast = inject(ToastService);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
let errorMessage = 'An error occurred';
if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
// Handle specific status codes
switch (error.status) {
case 401:
// Redirect to login
router.navigate(['/login']);
toast.error('Your session has expired. Please log in again.');
break;
case 403:
toast.error('You do not have permission to perform this action.');
break;
case 404:
toast.error('The requested resource was not found.');
break;
case 500:
toast.error('A server error occurred. Please try again later.');
break;
}
}
console.error('HTTP Error:', errorMessage);
return throwError(() => error);
})
);
};
app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { errorInterceptor } from './interceptors/error.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([errorInterceptor])
)
]
};
Retry logic
Simple retry
Retry failed requests automatically:import { retry } from 'rxjs/operators';
export class UserService {
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
retry(3), // Retry up to 3 times
catchError(this.handleError)
);
}
}
Conditional retry
Retry only for specific error types:import { retry, timer } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
export class UserService {
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
retry({
count: 3,
delay: (error: HttpErrorResponse, retryCount) => {
// Only retry on 5xx server errors
if (error.status >= 500 && error.status < 600) {
console.log(`Retry attempt ${retryCount}`);
// Exponential backoff: 1s, 2s, 4s
return timer(1000 * Math.pow(2, retryCount - 1));
}
// Don't retry for client errors
throw error;
}
}),
catchError(this.handleError)
);
}
}
Advanced retry with retryWhen
import { retryWhen, delay, take, tap } from 'rxjs/operators';
import { throwError, timer } from 'rxjs';
export class UserService {
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
retryWhen(errors =>
errors.pipe(
tap(error => console.log('Error occurred:', error)),
delay(1000), // Wait 1 second before retry
take(3), // Try up to 3 times
tap(() => console.log('Retrying...'))
)
),
catchError(this.handleError)
);
}
}
Timeout handling
Set timeouts for requests:import { timeout, catchError } from 'rxjs/operators';
import { TimeoutError } from 'rxjs';
export class UserService {
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
timeout(5000), // 5 second timeout
catchError((error) => {
if (error instanceof TimeoutError) {
console.error('Request timed out');
return of([]);
}
return throwError(() => error);
})
);
}
}
timeout option in the request:
http.get<User[]>('/api/users', {
timeout: 5000
}).pipe(
catchError(error => {
console.error('Request timed out or failed');
return of([]);
})
);
User feedback
Toast notifications
user.component.ts
import { Component, inject } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { catchError, EMPTY } from 'rxjs';
import { ToastService } from '../services/toast.service';
import { UserService } from '../services/user.service';
@Component({
selector: 'app-users',
template: `
<button (click)="loadUsers()">Load Users</button>
<div *ngFor="let user of users">
{{ user.name }}
</div>
`
})
export class UsersComponent {
private userService = inject(UserService);
private toast = inject(ToastService);
users: User[] = [];
loadUsers() {
this.userService.getUsers().pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 404) {
this.toast.warning('No users found');
} else if (error.status >= 500) {
this.toast.error('Server error. Please try again later.');
} else {
this.toast.error('Failed to load users');
}
return EMPTY; // Complete the observable
})
).subscribe(users => {
this.users = users;
this.toast.success('Users loaded successfully');
});
}
}
Loading states
Show loading and error states:user.component.ts
import { Component, inject, signal } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { catchError, finalize, EMPTY } from 'rxjs';
@Component({
selector: 'app-users',
template: `
@if (loading()) {
<div>Loading...</div>
}
@if (error()) {
<div class="error">
{{ error() }}
<button (click)="loadUsers()">Retry</button>
</div>
}
@if (!loading() && !error()) {
<div *ngFor="let user of users()">
{{ user.name }}
</div>
}
`
})
export class UsersComponent {
private userService = inject(UserService);
users = signal<User[]>([]);
loading = signal(false);
error = signal<string | null>(null);
ngOnInit() {
this.loadUsers();
}
loadUsers() {
this.loading.set(true);
this.error.set(null);
this.userService.getUsers().pipe(
catchError((error: HttpErrorResponse) => {
const message = error.status === 404
? 'No users found'
: 'Failed to load users. Please try again.';
this.error.set(message);
return EMPTY;
}),
finalize(() => this.loading.set(false))
).subscribe(users => {
this.users.set(users);
});
}
}
Specific error scenarios
Authentication errors
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';
export const authErrorInterceptor: HttpInterceptorFn = (req, next) => {
const router = inject(Router);
const authService = inject(AuthService);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// Clear session and redirect to login
authService.logout();
router.navigate(['/login'], {
queryParams: { returnUrl: router.url }
});
}
return throwError(() => error);
})
);
};
Network errors
import { catchError, of } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
export class DataService {
getData(): Observable<Data[]> {
return this.http.get<Data[]>('/api/data').pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 0) {
// Network error or CORS issue
console.error('Network error - check your connection');
// Return cached data if available
const cachedData = this.getCachedData();
return of(cachedData);
}
return throwError(() => error);
})
);
}
}
Validation errors
Handle validation errors from the server:import { HttpErrorResponse } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
interface ValidationError {
field: string;
message: string;
}
export class FormService {
submitForm(data: FormData): Observable<Response> {
return this.http.post<Response>('/api/submit', data).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 422) {
// Unprocessable entity - validation errors
const validationErrors = error.error.errors as ValidationError[];
validationErrors.forEach(err => {
console.error(`Validation error on ${err.field}: ${err.message}`);
});
}
return throwError(() => error);
})
);
}
}
Testing error handling
user.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { HttpErrorResponse } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { UserService } from './user.service';
describe('UserService Error Handling', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(),
provideHttpClientTesting(),
UserService
]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('should handle 404 error', () => {
service.getUsers().subscribe({
next: () => fail('should have failed'),
error: (error: HttpErrorResponse) => {
expect(error.status).toBe(404);
expect(error.statusText).toBe('Not Found');
}
});
const req = httpMock.expectOne('/api/users');
req.flush('User not found', {
status: 404,
statusText: 'Not Found'
});
});
it('should handle network error', () => {
service.getUsers().subscribe({
next: () => fail('should have failed'),
error: (error: HttpErrorResponse) => {
expect(error.error).toBeInstanceOf(ProgressEvent);
expect(error.status).toBe(0);
}
});
const req = httpMock.expectOne('/api/users');
req.error(new ProgressEvent('Network error'));
});
});
Best practices
Use centralized error handlingImplement a global error handler service or interceptor for consistent error handling across your application.
Provide meaningful feedbackShow user-friendly error messages instead of raw HTTP error messages.
Don’t swallow errors silently. Always log errors or notify users when something goes wrong.
Implement retry logic carefullyOnly retry idempotent operations (GET, PUT, DELETE) and avoid retrying POST requests that might create duplicate resources.
Handle different error typesDifferentiate between network errors (status 0), client errors (4xx), and server errors (5xx) for appropriate handling.
Use timeoutsAlways set reasonable timeouts to prevent requests from hanging indefinitely.
Next steps
HttpClient
Learn about making HTTP requests with HttpClient.
Interceptors
Transform requests and responses with interceptors.
