Video details

Error Handling in Angular - Complete Guide (2022)

Angular
09.05.2022
English

Unfortunately, errors happen in every application which makes error handling in angular apps one of the most critical parts you have to focus on. Yes, we can't get rid of errors completely, however, we can react to them and gracefully handle exceptions improving the user experience in our apps. In this video, I will share with you different error handling strategies in Angular that will help you to solve 80% of possible scenarios that might occur in your angular applications. You will learn how to handle exceptions that happen in sync and async code, how Angular tracks errors, and how it globally handles it. You will see how errors are being handled by rxjs streams and how to use Angular HTTP_INTERCEPTORS to implement a global strategy for failed HTTP requests.
💥
Angular courses made by Dmytro - https://bit.ly/df-courses 💥 ✂️
Use coupon YOUTUBE_DISCOUNT to get a 10%-off discount ✂️
🕒
Time Codes: 00:00:00 - Intro & What you will learn; 00:01:36 - Project overview; 00:04:18 - Using try/catch to handle errors in sync code; 00:06:23 - (optional) Why would we need global error handling; 00:07:19 - How works built-in Angular Global Error Handler; 00:09:30 - Implementing Global Error Handler; 00:15:12 - Why do we need to rethrow exceptions? 00:17:11 - try/catch, Angular and async code; 00:18:06 - What happens under the hood? 00:22:33 - Error handling in RxJS Observables; 00:31:11 - Global HTTP Error Handling; 00:38:10 -Bonus: Error flow vs Data Flow in RxJs; 00:40:24 - Outro;
🔗 Source code on GitHub (init state on the master branch): https://github.com/DMezhenskyi/angular-global-error-handling-example
💡 Short Frontend Snacks (Tips) every week here:
Twitter - https://twitter.com/DecodedFrontend Instagram - https://www.instagram.com/decodedfrontend LinkedIn - https://www.linkedin.com/in/dmezhenskyi

Transcript

I hope it's recording. Let's go. Hi everyone. Welcome. My name is Mitramshansky, and from this video you will learn everything you have to know about error handling in angular. You will never be confused why things work as they work. This video is long, like 40 minutes or so. So be ready. Be ready that during this 40 minutes we will be handling different types of errors using different strategies. We will be handling them locally in components, we will be handling them globally across the whole application. We will be catching exceptions in synchronous code and also in Asynchronous RxgS streams. And of course, we will have a look at the angular source code to learn what internal mechanics are involved in this error handling process. I have handling of Http errors using Http interceptors, and the implementation of the progressive replay strategy for the failed request is also included. However, guys, despite all this cool stuff, the goal of this video is not to give you some ready architectural solution, but the goal of this video is to give you knowledge that will help you eventually build your own architecture that fits your needs. And if you're interested in it, then let's get started. All right, let's imagine that I've got the following application that contains some backlog widget. And this widget is just a simple angular component that pulls some data from the server and renders the list of tasks as a list right here. Also it has a button, and when the user clicks this button, it throws an exception that you can currently see in the console. So my goal is to somehow handle the error and notify the user about that by showing some, I don't know, error message or banner in the view. Before we start working on that, let's quickly have a look at how it looks. The widget component in the source code. I will not be stopping on it too much because the source code of this exact current application state is available on the GitHub. Just follow the link in the video description so you can easily clone it and then investigate it and figure out how it was built. But sure, I will highlight the most important parts. So the widget code is mostly located under the corresponding folder. And here is the component itself. Yes, I use standalone components in this project, but it doesn't make any real difference to what I'm going to show you. Here you can see I inject the service that knows how to work with widget data. For example, it allows me to load the list of tasks by calling the load method. So I can subscribe to this observable in the template using a sync pipe and then render the list of the tasks using Ng four directive. If the list of tasks is empty, then they will be shown the following placeholder. So as you can see, it is quite a standard pattern, right? So besides that, there is a property error. And if it's not now, then there will be shown widget error component that takes the error as an input parameter and then it just renders the error message in its template. That's it. It cannot do anything else, actually. And last but not least is the button that handles the click event and the add task handler calls the ad task sync method that causes actually the error you saw in the browser console. It happens because I provide an inappropriate ID parameter for the task and it will be my first issue I have to solve. So once the exception is thrown, I have to catch it and assign the error to the error property in my class and in such a way they should be rendered the widget error component and the user will eventually see the error message. So, because this code is synchronous, we can easily catch the error by using a very well known try catch operator and moving the code with which potentially can throw an exception into the try block. The exception thrown in this block will end up inside the catch block and will be provided as a parameter for this operator. So you can then assign the error to the error property of the component and yeah, sure, because by default the error has the type unknown. You have to narrow the type before working with that. So now, as you can see, the error is gone, all right? And now we can save changes and check if it is working. So I open the browser and nice, you can see that the bunner is shown and you can see that there is no error in the console anymore. So the solution using tricage is working and it works well. However, it is not a silver bullet and it can work only with a limited range of cases. First, it should be a synchronous code because dry catch cannot work with a synchronous stuff. Secondly, you can use it only when you have some specific behavior on exactly this exception and exactly this use case. But what to do with unknown exception that might be also thrown? I mean, the error can happen everywhere and we cannot predict them. All right? So you can call some not existing method for some object in there on a neat lifecycle hook or anywhere else, for instance, somewhere outside the trikage block. I don't know, it happens very often. I'm pretty sure that only today you encountered at least one such a bug. But yeah, it doesn't matter that we should ignore this kind of exceptions. It would be great to have some centralized place where all exceptions eventually will end up and actually such a place already exists in angular. Let's have a look at this exception one more time. As you may notice, the error message doesn't have the string uncut error that happens when you throw the exception. And this exception was not caused by any try catch block. So the first conclusion we can do is that there is some global try catch block that kind of wraps our application and catches exceptions. Secondly, we see the error message. So it means that something prints this error. To figure out what happens under the hood, let's investigate a call stack that leads us to this error message. So let's check which line of code printed this error. Okay, so here is the string and it is a part of the handle error method that belongs to some error handler. It gets interesting, right? It means that angular has already some global error handler. Now I would suggest you check how exactly the error handler was involved. So I will open the second script in the error call stack and it looks like there is a handle error function that fetches the error handler from the angular root injector. If it is so, then it means that we could using Angular dependency injection to provide an alternative implementation for error handler using dependency providers like usedclass or use existing. This might be very convenient and useful because usually we don't want just to consolidate the error, right? We want to additionally somehow handle it and maybe save it in some remote database. So let's try to implement our custom global error handler and provide it for our application. So first of all, we would need to create an additional service. So let's do that using Angular CLI running these comments, you can create it. Here we go. And because I will be providing it using the dependency provider, I can remove the provided in property and let's maybe remove the service suffix. I don't know, you can leave if you want, of course, but yeah, to make sure that this error handler properly works, we should create a method that will be eventually called by angular. So let's create a handle error method just like that and to make sure that we didn't miss anything and that this class can safely replace the default error handler, we should implement an appropriate interface that is called error handler. And here you can already see the benefits of using interfaces because it already indicates that I have wrongly implemented the class method and namely I made a typo in the method name. If I didn't use the interface, I would know about this error only when I run my code in the browser and hopefully on the development server and not on the production one. So as you can see, interfaces are very useful and additionally the interface give me a hint of what kind of arguments takes this method. In my case, it is only one argument error. So I will take it here and the type of it will be unknown because it is more safe type compared to any and as I said, it is because the unknown will force you to narrow the time before it can be used. Likewise, we did a few minutes ago with checking the instance of the error. And by the way, if you are interested in such small but useful tips, consider subscribing to my other social media profiles. There, I constantly publish some small tips and tricks regarding front end development, and links will be in the video description so you can find them there. But let's move on. Because it is a regular service, this error handler, you can do anything you want here. You can inject Http client service and save the exception in some database. Or you can use some third party tools that allow you to save and track your client side errors and then proactively fix them. In my case, I will be showing some messages using Angular Material snack bar and console log the message in the browser console and to use the Snap bar, I have to import it first. So I will go to the main TS file and import the Material snack bar model into the Import Providers from Helper function. It will help me to provide appropriate providers for the snake bar. Sure, don't forget to import this model from the Angular Material snack bar like that. And by the way, if you use Ng models, then you would need to import this model in your app model TF file. Okay, now we are ready to inject the snack bar and use it in the error handler. So when the error happens, we show the message that we already know about this error and we are working on it somewhere like that. Then I just provide the text for the action button and say that the snack bar should automatically disappear after 2 seconds. And yeah, let's maybe also consolidate the error message somewhere here just to see which error has been caught and by which exactly handler. Okay, cool. Now we should replace the default error handler with this new one. And to do that, we should go back to the main TS file and add one more provider here. Here we provide the error handler token, but we also say that for this token, please use the glass instance of class error handler instead of the default one. Yeah, that's it. Now the custom error handler will be involved for any exception in our angular app. It should say any uncut exception in our angular app. So let's try to test it out. I go back to the browser, click the button and you can see that I have got a snack bar message. And also there is an error printed in the console. So our global handler works fine, but let's try to go back to our code and remove an artificial error we intentionally made right here. And as you can see, my component handled the error and showed the banner, but the exception didn't reach my global error handler. Why does so? Well, the reason is that once you handle the exception in some of the catch blocks, the exception becomes so to say invisible to the rest of the application. It is just handled and done. But what if we need to handle this exception on different levels, like locally in the component, and then propagated to the global handler that can do some additional operations on it? In this case, we have to restore the exception from the catch block. So the exception will be propagated to the next try catch operator, which is in our case some global try catch block used by angular internals. And that global catch operator will catch this error and will involve our custom global error handler and pass this exception to the handle error method. You can think about the exception restrawing as a hot potato that is being thrown from one catch operator to its parent. And then it continues to do so until some catch operator will silently handle this exception, or until it will be thrown away from the latest catch block and then it will become the uncalled exception. Remember this analogy. It will help you later to understand error handling in observables. Here we are slowly doing the transition to the sync world. And let's talk about exception handling when we deal with a sync code. As I said, try Catch works only with synchronous code. So it means that if I wrap this method in some function like set timeouts or something like that, the exception will not be handled. And let's make sure in it I click the button and what I see indeed the exception wasn't called by my component. So you don't see the error message banner. But what is interesting is that it was still caught by my global handler and the snack bar for the reason appeared at the top and it doesn't automatically disappear. By the way, to understand why it is working like that, we have to investigate the source code of the angler a little bit. And I know you love it. So let's go back to the code and investigate the bootstrap application function. And by the way, things you will see here, they're pretty much the same as for applications that are being bootstrapped using energy modern. So don't worry about that. All right? This function builds provider config from providers, we declared. It happens right here. And then it calls the internal create application function. There have been many interesting things like creating the platform injector, creating the Ng zone, running this zone and creating the root environment injector. When we talk about the root application injector, we talk about exactly this one, by the way. And also here will be registered providers we declared in the mainst file. Okay? Then you can see that from this injector from this root environment injector angler gets the instance of error handler. And because we provided the alternative implementation, there will be an instance of custom error handler, but it will be associated with the error handler token. Okay, I hope it's clear. So let's move forward. And here you can see one suspicious gentleman. And please welcome the Error subscription. So, as you know, Angler uses an execution context called Zon GF. In short, Zon is responsible for tracking stuff in your application and notifying Angular about that. So angular can schedule the change detection cycle when the error happens inside some micro tasks like Settlement out the zone will push this error into the on error stream that Angular listens to. And once the error is received, it uses the global error handler and calls the handle error method. So it explains why the global handler didn't miss the exception that happened in the Asynchronous code. Besides that, this piece of code can explain our bug with the Snake Barn. The reason is that the error handler is called outside of angular, which means that the Asynchronous stuff like Set Time Out promise resolving anything that usually causes that change detection run inside this callback. When you run it outside of angular, those kind of sync events will be ignored and change detection cycle will not be scheduled. So to fix our bug, we have to run the code inside our handle error method inside handler again. Unfortunately, it is quite easy to do. I just have to go back to my custom error handler, and there I have to inject the Mg zone into my handler class just like that. Then I can use Method run, and the code inside the callback function will be executed again within the angular zone, and it should fix the bug we experienced before. So let's try to again click the button and you can see that the snack bar appeared at the bottom of my screen and it automatically disappears. Awesome. All right. We have learned so much so far, and if you are still with me, and if you enjoyed this video, it is a great time to subscribe to my channel and hit that thumbs up, because it motivates me to create more such indepth videos about envelope. But let's move forward and talk about observables. Fortunately, I have one in my Widget component, and this is an Http call to the server that fetches the list of to do tasks currently. Please don't pay too much attention that it is an Http call. The things that I will show you in this part can be applied to any observable and about Httpspecific stuff, we will talk a little bit later in this video. So first, let's make this observable emit the error by modifying the URL that will cause the four or four Http error. Now, if we go back to the browser, we will see that our error was handled by the global error handler, as we would expect, right? But because error inside the Eraxia stream completes the observable, we might have some unexpected side effects in our UI. So we have to somehow handle errors inside RxgS streams as well. So the one and the most well known way to catch the error in the stream is to use an appropriate handler inside the subscribe method of the observable, something just like that. But in our case it won't work because we do subscribe in the template using a sink pipe. So we need another strategy. So it is good to have as less side effects as possible and usually should try to avoid leaking the data outside of the stream. But sometimes you would need to do the side effect and update some component property when error occurs. And in this case you could use a tab operator and provide the configuration object where you can define the callback in case of an error. So now if I save it and check, we can see that at least we can see the banner with some error message like at least something, right? But what if I want to handle it silently as if no error happened? What if in case of error, I just want to edit some Mt array to the stream so the UI will only show some placeholder visible that tells users that there are no to Dos in the list. For that we have to use the catch and replace strategy and it boils down to the following thing. First we have to catch the error using a corresponding RxgS operator called catchError. This operator takes a callback where the first argument is an actual error and it should return an unobservable that will replace the error one. As I told you, I want to return just an empty array. So I create a new observable using the off operator and provide an array of an argument just like that. Now, if we go to the browser, you can see that there is a placeholder appearing. And pay attention, there is no error banner shown why it is so this is because we caught the error and replace it with another value. So operators downstream, they know nothing about the error that happened before. But what if you want to do both? You want to show the error message or error banner in the widget and also you want to replace the error stream with an observable of an empty array and show this nice placeholder. Well, the head on solution would be to move the catch operator from the service and place it right after the top operator. And here make sure that it comes after the tab. Because if you do it another way around, you will get the same effect as before. Your error will be caught by catch error operator and replaced before it reaches the error callback inside the tab operator. Okay, so if I save it and check right now, you will see that they are both the placeholder and the banner message. So the flow for this exception was the following the load method does Http call. The call fails, so the error goes through the top operator and the error callback. The error callback updates the error property showing the error banner, and then the error meets the catch error operator that replaces it with an observable of an empty array. So basically, our Async pipe knows nothing about the error and subscribes to the observable we substituted inside the catch error operator, and it receives an empty array as a value. Okay, I hope it's clear, but what if I want to handle the same error also on the service side? Maybe I want to modify the error message or do some other stuff, I don't know. So I would need to revert the catch operator inside my Widget service. And let's imagine that I would need to perform some logic on this error right here, so something like that, and then I should return something from it. But what if I return an observable, the catch error operator in the component will not be invoked. So likewise with try catch operator, I have to rethrow the error downstream, and I can do it using the throw error operator. And in its callback, I can rethrow either the original error or I can replace it and replace it with some new one like that. So this exact error will be propagated downstream to the tab operator and eventually caused by the catch error operator and replaced with an empty array. Do you remember the analogy with the hot potato? And try catch operator. So here it's basically the same. The exception, when it happens in the stream, it is captured by the closest catch error operator, and if the exception is withdrawn, it goes to the next catch error operator in the pipe, and it continues to do so until the error reaches the error handler in the subscriber and eventually it ends up in the global handler. And by the way, let's check if our code actually works. And yeah, as you can see, the error was handled by the service. Then this error was replaced by a new one and was rethron. So the tab operator made the banner show up with the new error message, and after that, the second catch error operator was invoked and replaced the error with an empty string. So you can see this no task placeholder. And if you remove, by the way, the last catch operator that replaces the broken stream with an empty array, then you can see that the exception will be intercepted by the global error handler. But then you will not see this no task placeholder. All right, I hope I could make it clear to you how the error sort of, say, travels through that catch error operators inside the stream. And now let's talk about Http global error handling. Okay, currently we properly handle the error on the component side, and we do it also on the service side, but it works only for exactly this Http call. And the thing is that sometimes we want to apply some logic globally to each Http request in our application. For instance, I would like to implement some retry strategy. So if any Http request in my application fails, then Angular should give two or three more attempts before it throws the Http response exception. So I need some say, hook that would allow me to listen to every Http call in my application. And there is such a thing in Angular, and it is called angular Http interceptors. You can create an interceptor using angular CLI and generate one like you can see right now there is a dedicated intercept command and you can see that it is just a simple class that implements the Http interceptor interface and implements the interceptor method where you might perform some request transformation and pass the request to the next handler using this next handle method. It happens for every Http call in your application. Okay? And here you can apply already some operators that would eventually implement your global Http error handling strategy. As I said, I would like to implement retry strategies. So inside the pipe I will apply the retry operator and in its config I will define that before the error is thrown. There should be three additional attempts to make this request. And also I can define the interval between those three attempts. I can provide a static value like 1 second, but also I can implement progressive retrieve strategies. So the interval between calls will be always increasing. For that I have to provide a function that takes the error and current attempt count and it should return some observable. In my case it will be a timer that will be meeting a value with a delay that increases with every next attempt. So it will be like 1 second delay between first attempt, 2 seconds between next attempt, and 3 seconds between second and third attempt, and then they will be thrown the error. Then this error will be propagated to the catch operator in the Widget service. Then it will be rethallenged from there and caught again by the catch error operator in the component. But to make it work, we would need to register this interceptor in our application, because currently Angular knows nothing about that. You can easily do that by going to the main TF file and providing the following things. You have to provide an Http interceptors token and then using the use class dependency provider, you provide the global Http error handler. And because we can have many operators, sorry, interceptors, we have to make it as a multi provider just like that. Cool. What else? You could also use the catch operator here inside the interceptor and perform some logic there. For instance, let's just console log some message here to see the execution order in the browser. Don't forget to retro it, because otherwise the downstream catch error in the Widget service will not know about the error. And let's maybe add some message here and print that the error was withdrawn, something like that, okay? And let's do maybe the same, but for our digit service. So I will do something like that and also I will do the similar in the component. So we will drop a message right here that we update the error property and show the error banner. And inside the catch error operator we print a lock that the value for the failed stream will be replaced with an absorbable of an empty array also. So let's see how it finally works and what is the execution flow of it. Okay, I reload the page. You can see there are multiple attempts to do Http call. Then after three attempts, retry operator throws the error that was handled by the catch operator in the interceptor and then it was rethawn. Then it is called and rethown by catch error operator inside the widget service. And finally then it goes through the top operator in the widget component and catch error operator right here and here happens. They're placing the error with an empty array and in such a way we see both kind of placeholder and also the error banner. So I really hope that I made it clear to you how the exception flows through the aircraft stream when we add the Http interceptor as well. And by the way, our synchronous error handling is still there and handles errors locally in the component and withdraw them to the global error handler. And now, I promise that the last thing I know. You might be already very tired, but it confuses many developers who just started with RxJS. The question is, what about the operators like map, filter, et cetera. For instance, what if I have some map operator in my stream somewhere here that I don't know, transforms the data, whatever, these operators will be skipped in case of error. So when error occurs somewhere upstream, like in the widget service, then it falls down to the closest operator that can work with the errors actually. So it might be tap, catch error, retry, retry when operators and if I forgot something, please let me know in the comments. You can think about it as about two separate flows like data flow and error or exception flow. If there is no error data flows through the operators you all know is take, filter, map, distinct until change, throttle time, etc. E. And the operators like catch error, retry when they are ignored. But when the error is thrown inside the stream, then the opposite thing happens and error goes through the operators like catch error, retry, retry when, et cetera. And the rest of the operators like map, filter, take and others, they will be skipped. So in my case right now, you will see that the map operator was ignored. But if I go back to the widget service and fix the typo in the URL, then you can see that the map operator was involved, but catch error and retry, they were ignored. All right guys, that was it. I hope you are. Still with me. I hope you enjoyed this video and if so, please support my channel by sharing this video with your colleagues and friends and please let me know in the comments. Did you learn something new today from this exact video? That would be very interesting for me and maybe I forgot something then also write in the comments maybe you know other strategies, how you handle errors in your angular applications. I think it would be interesting discussion there in the comments. Also, don't forget to subscribe to my social media profiles to get different short tips and tricks about front end development. And of course, check out of my courses about angular links will be in the video description and I hope that's it. I wish you probably the week ahead. Stay safe and see you in the next video. Bye.