How do you implement global error handling?
Implementing global error handling in Angular applications is crucial for improving user experience, debugging, and maintaining application stability. It allows you to centralize the management of errors that occur anywhere in your application, preventing crashes and providing a consistent feedback mechanism to users.
Understanding Error Handling in Angular
By default, Angular provides a basic ErrorHandler that logs errors to the browser's console. While this is helpful during development, it doesn't offer a robust solution for production environments, as it doesn't notify users, send errors to a logging service, or gracefully handle application failures. To address this, Angular allows you to provide a custom implementation of the ErrorHandler.
Creating a Custom Error Handler Service
To implement global error handling, you typically create a service that extends Angular's built-in ErrorHandler class. This custom service will override the handleError method, allowing you to define your own logic for processing errors. You can differentiate between client-side (e.g., JavaScript runtime errors) and server-side (e.g., HTTP responses) errors and react accordingly.
import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';
// Assuming you have a NotificationService for user feedback
// import { NotificationService } from './notification.service';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error: any) {
// Use Injector to get services to avoid circular dependency with ErrorHandler
const router = this.injector.get(Router);
// const notificationService = this.injector.get(NotificationService);
let message: string;
let stackTrace: string | undefined;
if (error instanceof HttpErrorResponse) {
// Server Error
message = `Server error: ${error.status} - ${error.message}`;
stackTrace = error.error?.message || error.statusText;
// notificationService.showError(message);
} else if (error instanceof Error) {
// Client Error
message = `Client error: ${error.message}`;
stackTrace = error.stack;
// notificationService.showError(message);
} else {
// Unknown Error
message = 'An unexpected error occurred';
stackTrace = JSON.stringify(error);
// notificationService.showError(message);
}
console.error('Error caught by GlobalErrorHandler:', { message, stackTrace, originalError: error });
// Optionally, navigate to an error page or display a global notification
// router.navigate(['/error'], { queryParams: { error: message } });
}
}
Providing the Custom Error Handler
Once you have created your custom error handler service, you need to tell Angular to use it instead of the default one. This is done by providing it in the providers array of your root module (e.g., AppModule).
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { GlobalErrorHandler } from './global-error-handler';
// import { NotificationService } from './notification.service';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
// NotificationService // Provide your notification service if used
],
bootstrap: [AppComponent]
})
export class AppModule { }
Error Logging and Reporting
Inside your GlobalErrorHandler, you can integrate with various logging and reporting solutions. This ensures that errors are not only caught but also documented and sent to appropriate monitoring systems for analysis and resolution.
- Remote Logging Services: Send error details to services like Sentry, LogRocket, Bugsnag, or a custom backend logging API.
- User Feedback: Display user-friendly error messages or notifications (e.g., via a toast service) instead of crashing or showing raw error details.
- Contextual Information: Include additional data such as the current user, application state, component tree, or browser information to aid in debugging.
- Navigation: Optionally redirect the user to a generic error page or prompt them to reload the application.
Handling Specific HTTP Errors with Interceptors
While GlobalErrorHandler catches all errors, including HTTP errors, you might want more granular control over HTTP error responses (e.g., specific status codes like 401 Unauthorized, 404 Not Found, 500 Internal Server Error). For this, Angular's HttpInterceptor is the ideal solution. Interceptors can transform HTTP requests and responses, allowing you to catch HTTP errors before they propagate to the global error handler.
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';
// import { NotificationService } from './notification.service';
// import { AuthService } from './auth.service'; // For handling 401
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
constructor(
// private notificationService: NotificationService,
// private authService: AuthService
) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Client-side network error or JavaScript error
errorMessage = `Client-side error: ${error.error.message}`;
} else {
// Server-side error
switch (error.status) {
case 401:
errorMessage = 'Unauthorized: Please log in again.';
// this.authService.logout(); // Redirect to login
break;
case 404:
errorMessage = 'Resource not found.';
break;
case 500:
errorMessage = 'Internal server error. Please try again later.';
break;
default:
errorMessage = `Server Error: ${error.status} - ${error.message}`;
break;
}
}
// this.notificationService.showError(errorMessage);
console.error('HTTP Error caught by Interceptor:', errorMessage, error);
return throwError(() => new Error(errorMessage));
})
);
}
}
To activate the HTTP interceptor, you must provide it in your AppModule using the HTTP_INTERCEPTORS token and set multi: true.
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpErrorInterceptor } from './http-error.interceptor';
// ... inside @NgModule({ providers: [] })
providers: [
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
{ provide: HTTP_INTERCEPTORS, useClass: HttpErrorInterceptor, multi: true },
// NotificationService, // Provide your notification service if used
// AuthService // Provide your auth service if used
]
Conclusion
By combining a custom ErrorHandler for application-wide errors and HttpInterceptor for specific HTTP response handling, you can create a robust global error management system in your Angular application. This approach ensures that all errors are caught, processed gracefully, and reported effectively, significantly improving the stability and user experience of your application.