Do you know how to handle errors in Angular?

Last updated by Chris Clement [SSW] 7 months ago.See history

Angular provides built-in error-handling mechanisms such as ErrorHandler and HttpInterceptor.

Handling Uncaught Errors

By providing an ErrorHandler class, all uncaught errors will go to this service.

@NgModule({
  declarations: [...],
  imports: [...],
  providers: [
    {
      provide: ErrorHandler,
      useClass: ErrorHandlerService,
    }
  ],
  ...
})
export class AppModule { }
@Injectable({ providedIn: 'root' })
export class GlobalErrorHandlerService implements ErrorHandler {
  handleError(error: unknown): void {
    // Handle the error here, you can inject services
    console.error('Uncaught error!', error);
  }
}

The above example instructs Angular to use ErrorHandlerService as the default ErrorHandler in the scope of AppModule. Any injectable services can also be injected in the custom ErrorHandlerService, allowing various actions to be done in this service.

Scoping Error Handler

With how Angular treats the ErrorHandler as an injectable service, we can also control the scope of the ErrorHandler.

For example, if we want to provide a global error handler as a fallback and a specific error handler for a route, we can create a lazy-loaded module for that route and then provide another ErrorHandler. This approach will override the existing ErrorHandler for this particular route. See also Limiting provider scope by lazy loading modules.

@NgModule({
  declarations: [...],
  imports: [
    RouterModule.forChild(...),
    ...
  ],
  providers: [
    {
      provide: ErrorHandler,
      // HomeErrorHandlerService is the ErrorHandler for routes under this lazy-loaded module
      useClass: HomeErrorHandlerService,
    }
  ],
})
export class HomeModule { }

angular scoping error handler
Figure: Scoping Error Handler in Angular

Figure: Scoping Error Handler in Angular

Handling API Errors

All failed API calls not handled (i.e. not caught) will be handled by ErrorHandler. There are several ways to handle API errors by hand in cases where we need to do a specific error handling for an API; some of them are:

  • In Subscribe method:

    this.http.get('/foo').subscribe({
      next: response => {
        // Handle response
      },
      error: err => {
        // Handle error
        console.error('Error while calling API', err);
      },
    });
  • In pipe using catchError():

    this.http.get('/foo').pipe(
      catchError(err => {
        // Handle error
        console.error('Error while calling API', err);
        return of(null); // Return fallback value
      })
    ).subscribe(response => {
      // Handle response
    });

Catching API errors in HTTP Interceptor

Angular provides a mechanism to intercept HTTP calls using HttpInterceptor. Interceptors behave like middleware for API calls. HttpInterceptor provides many functionalities, one of which is to alter how API calls behave - specifically on error.

Below is an example of how to handle failed API calls directly in the HttpInterceptor:

// app.module.ts
@NgModule({
  declarations: [...],
  imports: [...],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: GlobalHttpInterceptorService,
      multi: true,
    },
  ],
  ...
})
export class AppModule { }

// global-http-interceptor.service.ts
@Injectable({ providedIn: 'root' })
export class GlobalHttpInterceptorService implements HttpInterceptor {
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      catchError(err => {
        // Handle API error
        console.error('Error while calling API', err);
        return of(err);
      }),
    );
  }
}

Note that since we can manipulate how we return the original request with next.handle(req), we can also implement more advanced patterns like retries.

Use ProblemDetails

A structured error message is needed to communicate errors effectively between API and frontend so the frontend application can identify errors correctly and show the right user experience for the fitting errors.

One of the standard structures is using the ProblemDetails format. Read more about this on Do you return detailed error messages? .

Using ProblemDetails, we can identify the errors, extract information from the error payload, and act appropriately based on the error.

In the example below, we show a message box showing the error message from the API.

// Example error payload
{
  "type": "https://example.com/probs/invalid-id",
  "title": "Invalid ID",
  "status": 400,
  "detail": "The provided ID has invalid characters.",
  "instance": "/account/12%203",
}
this.http.get('/foo').subscribe({
  next: response => {
    // Handle response
  },
  error: (err) => {
    // Handle error
    if (!(err instanceof HttpErrorResponse)) {
      console.error('Error while calling API', err);
    }

    if (err.error.type === 'https://example.com/probs/invalid-id') {
      // Show error message
      this.snackbar.open(`${error.title} - ${error.detail}`);
      return;
    }

    console.error('API error', err);
  },
});
We open source. Powered by GitHub