You're probably wondering why I'm writing a blog post on error handling, since we all write code that never has errors. Right?
If you're anything like me, you know that's just not true. Errors are obviously part of development. We obviously work to eliminate as many as possible. But gracefully handling them when they occur is also really important. It lessens the impact of those errors when they occur.
So with that in mind, I set off to learn how to customize the error handling process in an Angular app. I previously wrote a blog post detailing how I used HTTP Interceptors to capture HTTP errors on the way back from the server and showing them to the user. This post briefly touches on that as well as covering another type of error that occurs in Angular apps. If you need an example of how to implement these error handling options, check out this example on StackBlitz. Let's get started!
First, let's talk about the two ways I mentioned above that you can capture and handle errors in your application. Let's start with providing an error handler. Angular's documentation says the following about the
Provides a hook for centralized exception handling. ... The default implementation of
ErrorHandlerprints error messages to the console. To intercept error handling, write a custom exception handler that replaces this default as appropriate for your app.
In essence, implementing the
ErrorHandler class allows you to customize the way your application handles errors. Instead of just using
console.error to output the error to the console, you could send the error to a logging service, or log the error to Sentry, or toast a message to the user, or whatever else you could think of. It's really pretty great that you have that fine-grained control over your application.
(Speaking of Sentry: anyone who needs a way to log and track errors that occur in their application, Angular or otherwaise, should use it. It's pretty incredible what it offers. Go check it out!)
Another way to handle errors in your Angular application is by implementing an HTTP Interceptor. Angular.io says the following about the
Intercepts HttpRequest and handles them. ... Typically an interceptor will transform the outgoing request before returning next.handle(transformedReq). An interceptor may choose to transform the response event stream as well, by applying additional Rx operators on the stream returned by next.handle().
HttpInterceptor to handle errors, you'll be grabbing the response event stream and catching the errors and doing something with that error. Your options are the same as above: log the error, toast a message, etc.
What I learned about these two errors is that they handle different use cases. I tried to use the
ErrorHandler class to handle all errors in my application. But it wasn't properly catching HTTP errors. Sometimes it would catch them, but then sending the error to my alert toasting service wasn't doing anything. But catching other errors thrown in the application worked perfectly in the custom
ErrorHandler. As soon as I implemented the
HttpInterceptor interface, the HTTP Errors were properly caught and sent to the service and showed up in the application. So it appears to me that the
ErrorHandler class shouldn't be used to handle any sort of HTTP error.
I'll be honest: this surprised me, and was a bit of a bummer. I was hoping to be able to write the code to handle all errors in one spot and show the user. On the other hand, it does kind of make sense. The two types of errors are different. It makes sense that they're caught and handled in different ways. Maybe you want to retry failed HTTP call, but just want to output the error to the console if you try and access an undefined attribute. This setup allows for that.
There is one last thing to remember, though when capturing HTTP errors with the
HttpInterceptor. Catching the errors there and toasting them isn't always quite enough. You may also need to ensure that you catch the error where you made the call as well. Let's say you make a call on the profile page of your app to get the user's information. You may very well show a loader while the info is being obtained. If that call fails and you don't catch the error, that loader will never go away. So keep that in mind, that you can add the error handler that toasts the error in one spot for all calls, but you probably will want to catch some or all of the calls as well wherever they're called*.
Hopefully you now understand error handling in your Angular application a little better. Whatever you need to do when an error occurs in your app, you have control over it.
Since publishing this post, I learned one other thing about catching the errors in the interceptor service. When you use the
.pipe() function on the rxjs Observable in the interceptor service, you can either
tap into the stream, or use the
catchError function; both work. However, there's a big difference. If you use
catchError, it all ends right there. The error is caught, but you can't then also catch the error in the component that called the API. So, if you did show a loader element when you submitted a form, you couldn't hide it after the error occurred. If you use the
tap function, you can still view the error, toast the error message, and then also catch the error in the calling component. Here's a brief example in code to help explain this: