Mastering Error Handling in Angular: A Comprehensive Guide

Unleash the power of robust error handling in Angular. From managing exceptions in synchronous and asynchronous code to handling HTTP errors, this guide helps you enhance your Angular applications like a pro.

Mastering Error Handling in Angular: A Comprehensive Guide

Errors are inevitable in any application, making error handling a critical aspect of software development.

This is particularly true for Angular applications/boilerplates, where a robust error-handling strategy can significantly enhance the user experience.

This article will explore 4 simple and most common error-handling strategies in Angular that can address almost all scenarios in your applications.

1. Handling Errors in Synchronous Code

One of the most straightforward ways to handle errors in synchronous code is by using the try/catch block.

This allows you to catch exceptions at the point where they occur and handle them immediately.

Here's a simple example:

try {
  // Code that may throw an exception
} catch (error) {
  // Handle the error
}

In this code snippet, the try block contains code that might throw an exception, and the catch block is where you handle or catch the error if it occurs.

However, this approach might not be sufficient for larger applications, where errors can occur in various parts of the codebase.

So let's go to the next strategy - the global error handling.

2. Global Error Handling in Angular

This is where Angular's built-in Global Error Handler comes into play.

The Global Error Handler is a service that provides a centralised place for handling exceptions.

Angular routes it to this handler when an error occurs, allowing you to manage all errors in one place.

Implementing a Global Error Handler involves creating a class that extends Angular's ErrorHandler class. This class can then override the handleError method, providing custom logic for handling exceptions.

Let's see an example:

import { ErrorHandler } from '@angular/core';

export class GlobalErrorHandler extends ErrorHandler {
  handleError(error) {
    // Custom error handling logic
    throw error;
  }
}

In this example, we create a new class GlobalErrorHandler that extends the ErrorHandler class from Angular.

We then override the handleError method to provide our custom error-handling logic. The throw error; statement ensures that the error is not silently ignored.

A straightforward step!

To use the GlobalErrorHandler in our application, we need to provide it in our application module. Here's how we can do it:

import { ErrorHandler } from '@angular/core';
import { GlobalErrorHandler } from './global-error.handler';

@NgModule({
  ...
  providers: [
    {
      provide: ErrorHandler,
      useClass: GlobalErrorHandler
    },
  ],
  ...
})
export class AppModule { }

As you can see, in this example, we import ErrorHandler from @angular/core and GlobalErrorHandler from its file.

Then, in the providers array of the NgModule decorator, we provide our GlobalErrorHandler for ErrorHandler.

This tells Angular to use our GlobalErrorHandler instead of the default ErrorHandler.

This strategy is very useful for scenarios when we want to catch errors from code we don't have access to, e.g., an external library, but not only! In general, it is a good approach to handle all your errors here if you haven't already—using the other strategies.

3. Error Handling in Asynchronous Code

When it comes to asynchronous code, things get a bit more complex.

Angular uses RxJS Observables to handle asynchronous operations using their error-handling mechanisms.

Let's see an example:

observable.pipe(
  catchError(error => {
    // Handle the error
    return of(defaultValue);
  })
);

return a default value in case of an error

In this code example, we use the catchError operator from RxJS to catch and handle errors in an Observable.

The catchError operator takes a function that handles the error and returns a new Observable.

This is useful when we want to EMPTY the chain, return a default value as we've seen in the example, or change the error before sending it further to other error handlers.

We can do anything here with error parameter.

observable.pipe(
  catchError(error => {
    // Change the error and send it further
    return throwError(new Error('Something happened'));
  })
);

Change the error and send it further

What we'll returnwhile handling will change the behavior or the current Observable chain.

observable.pipe(
  catchError(error => {
    // Complete the Observable chain here, don't send anything further
    return EMPTY;
  })
);

Complete an observable error

4. Handling HTTP Errors

One of the most common scenarios in Angular applications is handling HTTP errors.

HTTP requests can fail for various reasons, and handling these failures gracefully can significantly improve the user experience.

Angular provides HTTP_INTERCEPTORS token, which allows you to implement a global strategy for failed HTTP requests.

This can involve retrying the request, showing a user-friendly error message, or redirecting the user to a different page.

Let's see it in action:

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .pipe(
        catchError((error: HttpErrorResponse) => {
          // Handle the error
          return throwError(error);
        })
      );
  }
}

In this code snippet, we create an HttpErrorInterceptor service that implements the HttpInterceptor interface.

The intercept method is where we handle the HTTP request.

Then, we use the previously mentioned catchError operator to catch any errors while handling the request.

Pretty simple!

To add the HttpErrorInterceptor to the HTTP_INTERCEPTORS token, we need to provide it in our application module or any specific module where you want to use it.

Here's how we can do it:

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpErrorInterceptor } from './http-error.interceptor';

@NgModule({
  ...
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpErrorInterceptor,
      multi: true,
    },
  ],
  ...
})
export class AppModule { }

In this example, we import HTTP_INTERCEPTORS from @angular/common/http and HttpErrorInterceptor from its file.

Then, in the providers array of the NgModule decorator, we provide our HttpErrorInterceptor as a multi-provider for HTTP_INTERCEPTORS.

This tells Angular to use our HttpErrorInterceptor for HTTP requests across the application.

Conclusion

In conclusion, error handling in Angular is a multifaceted process that involves understanding and leveraging various mechanisms provided by the framework.

A robust error-handling strategy can make your Angular applications more resilient and user-friendly, whether handling exceptions in synchronous code, managing errors in asynchronous operations, or dealing with failed HTTP requests.

Don't ignore errors; make sure you always handle them. Whether it is a simple try/catch or global error handler, always return a message to your users, so they'll know what's going on.

These 4 strategies should be enough to handle all errors in Angular!