Error Handling with Angular Interceptors
Tags: angular, tutorial, interceptor, refactoring
Published: 2024-06-25 13:20:45

## Introduction

In this article I'll tackle the challenge of building a robust error handling in our user profile form application. I'll look beyond simple validation errors and dive into a wider array of issues that can arise during the HTTP communication with the backend server. What if there's no network connection, or the server sends us an unexpected response? To ensure a smooth user experience, we need to anticipate these errors and provide clear feedback or recovery options.

### What I'll cover

- **The need for comprehensive error handling** - look beyond validation errors and uncover into the different types of issues that can occur during HTTP communication.
- **The power of interceptors** - discover how interceptors can act as a central point for managing errors, validating responses, and enhancing security.
- **Creating and registering an interceptor** - the process of setting up an Angular interceptor.
- **Validating successful responses** - implement the logic to ensure that the server's 200 OK responses match our expected format.
- **Handling network errors** - learn how to detect and manage scenarios where the user loses internet connection.
- **Tackling other errors** - explore strategies for handling server-side errors and unexpected issues.

#### _A quick note_
- _This article builds upon the concepts and code I've developed in previous articles in this series. If you're just joining us, I highly recommend catching up on the earlier articles to make the most of this one._
- _You can find the code I'll be working with in the `16.user-service` branch of the [repository](https://github.com/cezar-plescan/user-profile-editor/tree/16.user-service)._

## Identifying the current issues

So far, I've focused on handling form validation errors within the `tapValidationErrors` operator - those 400 Bad Request responses from the server when the form data isn't quite right. However, there are other types of errors that can crop up, and we need a way to deal with them too. These include:
- **network errors** - the "no internet connection" scenario.
- **invalid response formats** - even if the server responds with a 200 status code, the data might not be in the format we expect.
- **unexpected errors** - the server could return various error codes, such as 4xx or 5xx, but other than the 400 Bad Request, which I already handled in the `tapValidationErrors` RxJS operator.

#### Current error handling limitations

Currently, error handling is primarily managed by the `UserProfileComponent`, using the `tapError` operator to set an error flag or display a popup message. Additionally, the `tapResponseData` operator assumes the response will always be in the expected successful format. We need to expand our error-handling capabilities to cover unexpected scenarios and responses with invalid formats.

## Introducing Angular interceptors

That's where Angular HTTP interceptors come into play. These handy tools let us intercept and handle HTTP requests and responses, giving us greater control over how our application communicates with the backend. 

They allow us to:
- **Catch errors globally** - Instead of handling errors in every component, we can catch them in one place.
- **Validate response formats** - We can verify that server responses match our agreed-upon structure.
- **Handle specific error types** - We can differentiate between various error scenarios (e.g., network errors, authorization errors, server errors) and respond appropriately.
- **Enhance security** - We can add headers, tokens, or other security measures to requests and responses.

### Creating and registering an interceptor

To get started, I'll create an interceptor in the `src/app/core/interceptors` folder using the Angular CLI command `ng generate interceptor server-error`. This will generate a file named `server-error.interceptor.ts`:{% embed https://gist.github.com/cezar-plescan/e6e0497fd1a1bccb1b9d0465b79ff210 %}

Next, I need to tell Angular to use this interceptor whenever we make HTTP requests with the `HttpClient` service. More specifically, I need to update the `app.config.ts` file:{% embed https://gist.github.com/cezar-plescan/38fab9ae566007fa4021f4a2df0b220e %}

At this point, our interceptor doesn't do anything yet. My first task will be to validate the format of successful responses, which have the HTTP "**200 OK**" status code.

## Validating successful response format

Recall that in the `tapResponseData` operator definition I've assumed that successful responses from the server follow a specific format.{% embed https://gist.github.com/cezar-plescan/72abccc47a9b7a9483e9c282cbdc6d95 %}

Let's recap that format defined within the `server.ts` file. It's an object of this type `{status: 'ok', data: any}`:{% embed https://gist.github.com/cezar-plescan/c924d27120306b7e0e35542a4c5d24ef %}

It's important to note that there is no single, universal standard for RESTful API response formats. Each application can have its own conventions. However, once a format is established, the client (our Angular app) should verify if the server's response complies with it. This helps catch unexpected errors or inconsistencies on the backend.

### Implementing the response format validation

Here's the updated interceptor, with the check for the format of 200 OK responses:{% embed https://gist.github.com/cezar-plescan/722608530b3165bf2b581e7181994d48 %}

The `check200ResponseBodyFormat` function verifies if a response matches the expected format. The interceptor taps into the HTTP response stream, checking if the response is a `200 OK` and if it fails the format check. If so, it displays an error notification using `MatSnackBar` and throws a custom error.

To see this in action, you can intentionally modify the `server.ts` file to return a malformed response with a 200 status code (e.g., change `status: 'ok'` to `status: 'bad format'`). Then, restart the server and reload the application. The interceptor should detect this error and display the notification.

### Check out the updated code

The updated code incorporating the changes made so far can be found in the repository at [this specific revision](https://github.com/cezar-plescan/user-profile-editor/tree/faf6536062cca0b4d84afc984dc8e4ce4e47ca9f). Feel free to explore the repository to see the full implementation details of the interceptor.

## Handling network errors

What happens when the user loses internet connection? This is a common scenario that we need to handle gracefully to provide a good user experience. To catch network errors, I'll leverage the `catchError` operator within the interceptor. This operator allows us to intercept errors in the HTTP request/response pipeline and take appropriate action. 

### Implementation

Here's how I'll modify the interceptor:{% embed https://gist.github.com/cezar-plescan/3d4c528867e6949e8e2269d5e4baa4c6 %}

Remember that network error detection can be tricky, and this implementation is just one approach. Depending on your application's specific needs, you might need to adjust or expand this logic further.

### How it works

1. The `catchError` operator intercepts any errors that happen during the request.
2. The `checkNoNetworkConnection` function checks if the error looks like a network issue. This function examines the error object for missing headers, a zero status code, and other clues.
3. If it's a network error:
   - Show a friendly message to the user ("No network connection").
   - Log the error so we know it happened (for debugging).
   - Set a flag `wasCaught` on the error to remember that the interceptor already handled it.
   - Re-throw the error. This is important! It lets other parts of the app know about the problem. For example, the `tapError` operator I created earlier can now use that `wasCaught` flag to avoid showing the same message twice.
4. If it's not a network error, I just re-throw it, letting other parts of the app deal with it in their own way.

### Updating the `tapError` operator

To ensure that we don't display multiple error notifications for the same error, I'll update the `tapError` operator to check the flag `wasCaught` on the error object. This flag is set by the interceptor when it catches a network error.

Here is the updated operator:{% embed https://gist.github.com/cezar-plescan/d9bdaf54c6b03918426012ba3fa1885c %}

Then, in the `UserProfileComponent`, I have to update the request stream pipeline in the `saveUserData()` method where the operator is used:{% embed https://gist.github.com/cezar-plescan/e691ef78da3bc6c3cb22d162236e4d01 %}

### Check out the updated code

The updated code with these changes can be found in the repository at [this specific revision](https://github.com/cezar-plescan/user-profile-editor/tree/15f5d1c17933ea73d48efa91d417747009508060).

## Handling other error types

While the interceptor manages invalid 200 responses and network issues, other error scenarios can still arise during server communication. For a robust user experience, I need to address these remaining errors as well.

### Implementation

I'll enhance the existing interceptor code to handle these additional errors:{% embed https://gist.github.com/cezar-plescan/70285bc1eec2e39313447eef19f29904 %}

While I won't cover authentication-specific errors (401, 403) in detail here (as these are typically handled by dedicated interceptors), it's important to have a strategy for dealing with unexpected server errors or other potential HTTP issues.

You might wonder, **why re-throw the error** after I've handled it in the interceptor? Here's the reasoning:
- flexibility - re-throwing the error allows for additional error handling at higher levels of our application; for instance, we might have a global error handler that logs errors or sends them to an error tracking service.
- component specific handling - our individual components might need to take specific actions based on the error; for example, our `UserProfileComponent` might want to display a more tailored error message in certain cases.

### Skipping validation errors

One important thing to note is that I'm not going to handle validation errors in the interceptor. Why? Because I already have the `tapValidationErrors` operator taking care of those. This operator is designed to catch errors that are related to the data we send to the server. The interceptor will let `tapValidationErrors` do its thing and focus on other types of errors.

### Removing `tapError` from the component

Remember the `tapError` operator I used in the `saveUserData` method? We don't need it anymore. Since we're catching all errors in the interceptor and showing the appropriate messages, there's no need for the component to worry about error handling.

### Checking Out the Updated Code

You can find the updated code incorporating these error-handling enhancements at the [following revision](https://github.com/cezar-plescan/user-profile-editor/tree/200f2f05eb2d089f8836e8b281e5b14ed9b99884).

Feel free to explore the repository, experiment with the code, and see how these changes improve your Angular application robustness in handling a wider range of HTTP errors!

## Further resources on Angular interceptors

While I've covered the fundamentals of error handling with Angular interceptors, there's always more to learn. If you're eager to dive deeper into this powerful tool, here are some resources that will help you level up your skills:
- [Angular 17 HTTP interceptors: A complete guide](https://medium.com/@mohsinogen/angular-17-http-interceptors-guide-417e7c8ffada): This guide covers everything you need to know about Angular interceptors, from the basics to advanced use cases and best practices.
- [Angular functional interceptors](https://medium.com/@santosant/angular-functional-interceptors-3a2a2e71cdef): Explore a modern, functional approach to creating interceptors, leveraging RxJS operators for clean and concise code.
- [Angular's 17 interceptors: Complete tutorial](https://dev.to/bytebantz/angulars-17-interceptors-complete-tutorial-220k): This tutorial provides a step-by-step walkthrough of building and using interceptors, with practical examples and explanations.
- [What is an Angular interceptor and how to implement it?](https://www.scaler.com/topics/angular/angular-interceptor/) This article provides a beginner-friendly introduction to Angular interceptors, explaining their purpose and demonstrating how to create and use them in your applications.

## Wrapping Up

In this article, we've leveled up our Angular user profile form by introducing an error-handling mechanism using an HTTP interceptor. I've tackled common challenges like:
- invalid response format - making sure the data we get from the server is what we expect, even when the status code is 200 OK.
- network errors - handling those "no internet" moments and giving the user helpful feedback.
- other server errors - catching and displaying messages for those unexpected server hiccups.

#### Check Out the Code

Ready to see it all in action? The complete code for this error-handling interceptor, along with the custom error classes and helper functions, can be found in the `17.error-interceptor` branch of the GitHub [repository](https://github.com/cezar-plescan/user-profile-editor/tree/17.error-interceptor).

Feel free to explore, experiment, and adapt it to your own Angular applications.

_Thanks for reading, and happy coding!_