How do you handle errors in Angular HTTP calls?
Robust error handling is crucial for any web application, especially when dealing with asynchronous HTTP requests in Angular. It ensures a better user experience, helps in debugging, and prevents application crashes. Angular provides powerful tools, primarily through RxJS operators and HTTP Interceptors, to manage and respond to errors effectively.
Understanding HTTP Error Types
HTTP errors can broadly be categorized into client-side (network issues, request misconfigurations) and server-side (status codes 4xx, 5xx). Differentiating between these helps in providing relevant feedback and logging.
Using `catchError` for Localized Handling
The catchError RxJS operator is the primary tool for handling errors that occur within an Observable stream. It allows you to intercept an error, perform an action (like logging or transforming the error), and then return a new Observable or rethrow the error.
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
private apiUrl = 'https://api.example.com/data';
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get<any>(this.apiUrl).pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Client-side or network error occurred.
errorMessage = `Client-side error: ${error.error.message}`;
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong.
errorMessage = `Server-side error: Status ${error.status}, Message: ${error.message}`;
}
console.error(errorMessage); // Log the error
return throwError(() => new Error('Something bad happened; please try again later.')); // Re-throw for component to handle
}
}
The `handleError` Function Explained
The handleError function is a common pattern to centralize the logic for processing different types of HTTP errors. It typically checks if the error is a client-side ErrorEvent or a server-side HttpErrorResponse and then logs the error and returns a user-friendly error message via throwError.
Global Error Handling with HTTP Interceptors
For a more centralized approach, HTTP Interceptors can catch and handle errors for all outgoing HTTP requests and incoming responses. This is ideal for logging errors, displaying global notifications, or refreshing authentication tokens.
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = `Client error: ${error.error.message}`;
} else {
// Server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
console.error('Caught by Interceptor:', errorMessage); // Log the error globally
// Optionally display a toast or modal here
return throwError(() => new Error(errorMessage));
})
);
}
}
Remember to register your interceptor in the providers array of your AppModule:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
// ...
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
]
Displaying Errors to Users
Beyond logging, it's essential to provide meaningful feedback to users when an error occurs. The chosen method depends on the severity and context of the error.
- Toast notifications or snackbars for transient messages.
- Modals or dialogs for critical errors requiring user interaction.
- Inline error messages next to form fields for validation issues.
- Dedicated error pages for unrecoverable application states (e.g., 404, 500).
Retrying Failed Requests
For transient network issues, the retry or retryWhen RxJS operators can be used to automatically re-attempt a failed HTTP request a specified number of times before propagating the error.
import { catchError, retry } from 'rxjs/operators';
// ...
getDataWithRetry(): Observable<any> {
return this.http.get<any>(this.apiUrl).pipe(
retry(3), // Retry up to 3 times
catchError(this.handleError)
);
}