What is Angular error handling strategy?
Angular provides a robust and flexible strategy for handling errors, encompassing both client-side and server-side issues. Effective error handling is crucial for creating resilient applications that can gracefully recover from unexpected situations and provide a good user experience.
Core Principles of Angular Error Handling
Angular's error handling strategy is built around several key mechanisms, primarily the global ErrorHandler service, RxJS operators for asynchronous operations, and HTTP Interceptors for network requests.
1. Global Error Handling with `ErrorHandler`
Angular provides a built-in ErrorHandler service. By default, it simply logs errors to the browser's console. However, you can extend this service to implement custom global error handling logic, such as sending errors to a logging service, displaying a notification to the user, or performing other recovery actions.
import { ErrorHandler, Injectable } from '@angular/core';
@Injectable()
export class CustomErrorHandler implements ErrorHandler {
handleError(error: any) {
console.error('An error occurred:', error);
// Log error to a remote service
// Display a user-friendly message
}
}
To use a custom error handler, you must provide it in your AppModule:
import { NgModule, ErrorHandler } from '@angular/core';
import { CustomErrorHandler } from './custom-error-handler';
@NgModule({
providers: [
{ provide: ErrorHandler, useClass: CustomErrorHandler }
]
})
export class AppModule { }
2. Asynchronous Error Handling with RxJS (`catchError`)
Angular applications heavily rely on Observables for asynchronous operations, especially HTTP requests. The catchError operator from RxJS is the primary way to handle errors originating from these observables.
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class DataService {
constructor(private http: HttpClient) { }
getData(): Observable<any> {
return this.http.get<any>('/api/data').pipe(
catchError(this.handleHttpError)
);
}
private handleHttpError(error: HttpErrorResponse) {
let errorMessage = 'Unknown error!';
if (error.error instanceof ErrorEvent) {
// Client-side errors
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side errors
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
console.error(errorMessage);
return throwError(() => new Error(errorMessage));
}
}
catchError allows you to intercept an error notification, perform some logic (like logging or transforming the error), and then return a new observable, potentially one that emits a default value or re-throws a new error.
3. Centralized HTTP Error Handling with `HttpInterceptor`
For handling HTTP errors consistently across your entire application, HttpInterceptors are invaluable. An interceptor can catch all outgoing HTTP requests and incoming HTTP responses, allowing you to centralize logic for error handling, authentication, logging, etc.
import { Injectable } from '@angular/core';
import {
HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, 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 = `Error: ${error.error.message}`;
} else {
// Server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
console.error('HTTP Error via Interceptor:', errorMessage);
// You can also display a toast or redirect based on error status
return throwError(() => new Error(errorMessage));
})
);
}
}
To register the interceptor, add it to your AppModule providers:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ErrorInterceptor } from './error.interceptor';
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
]
})
export class AppModule { }
Best Practices for Angular Error Handling
- Log Errors Effectively: Ensure all errors, especially those caught globally, are logged to a robust monitoring system (e.g., Sentry, New Relic, custom backend).
- Provide User Feedback: Display user-friendly messages for errors. Avoid showing raw technical error details to end-users.
- Differentiate Error Types: Handle client-side (network issues, syntax errors) and server-side (API issues, database errors) errors differently.
- Centralize Where Possible: Use
ErrorHandlerfor application-wide uncaught exceptions andHttpInterceptorfor global HTTP errors to avoid repetition. - Graceful Degradation/Recovery: Implement retry mechanisms (e.g.,
retry,retryWhenRxJS operators) for transient network issues, or provide fallback data. - Component-Specific Handling: For specific scenarios where a global approach isn't sufficient, handle errors within the component or service using
catchErrorlocally. - Test Error Paths: Thoroughly test how your application behaves when errors occur to ensure robustness.