Browse Source

Merge pull request #18989 from abpframework/sinan/article

Global error handling in Angular - Article
pull/18992/head
Sinan Öztürk 3 years ago
committed by GitHub
parent
commit
314a7c5dae
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 266
      docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/POST.md
  2. BIN
      docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error-image-1.png
  3. BIN
      docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error_handling.jpg
  4. BIN
      docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-4.gif
  5. BIN
      docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-5.gif
  6. BIN
      docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-7.gif
  7. BIN
      docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-8.gif
  8. BIN
      docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/show-toast-2.gif

266
docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/POST.md

@ -0,0 +1,266 @@
# Global Error Handling in Angular
![Error Handling](error_handling.jpg)
Error handling is how we deal with errors that go wrong when we are running a program. There is no code that runs perfectly forever :) Things can go wrong and your application might crash. **So, in order to run your program smoothly you must handle errors.** It is just not for keeping your application in a running state. It is also useful to show messages about the error to the client. Like what went wrong, why it is not allowed to access this page etc.
### How to handle errors
- First of all, you have to catch them 😀. You can catch them via **try-catch block**. See an example;
#### Create an basic error
```ts
({} as any).doSomething()
```
![Error Image](error-image-1.png)
#### Handle it
```ts
try {
({} as any).doSomething();
} catch (error) {
this.toastService.showError(error.message);
}
```
![toast-gif](show-toast-2.gif)
- See, we catch the error and handle it.
- In this case, we know where the error will be thrown. Most of the time we won't know where the error will appear. Should we cover the entire application with try-catch blocks? Of course not 😀
- **We are going to handle errors globally**. Angular provides a great way to do it. Let's do it step by step;
### 1.Create a **service** and implement the **`ErrorHandler`** interface.
```ts
import { ErrorHandler, Injectable, inject } from '@angular/core';
import { ToastService } from './toast.service';
@Injectable({
providedIn: 'root'
})
export class CustomErrorHandlerService implements ErrorHandler {
toastService = inject(ToastService);
//This method comes from interface
handleError(error: any): void {
this.toastService.showError(error.message);
}
}
```
### 2.Provide the service by using the **`ErrorHandler`** class from **`@angular/core`**.
```ts
import { ErrorHandler } from '@angular/core';
providers: [
{ provide: ErrorHandler, useExisting: CustomErrorHandlerService }
]
```
![toast-gif](show-toast-2.gif)
- It behaves exactly the same. Nice, now we catch the entire errors in one simple service.
- Is it that simple? I wish it is but it's not 😀. This handling mechanism only works synchronously. When we start making http requests, our **`CustomErrorHandlerService`** won't catch the errors.
## How to handle HTTP Requests
Make an HTTP request and check if it's working.
![http-request](http-request-4.gif)
As you can see it doesn’t work. So how can we catch the http errors? with **`catchError()`** operator in rxjs or **`observer object`**. I will go with **`catchError()`** operator.
```ts
getTodo(id: number) {
this.http
.get(`https://jsonplaceholder.typicode.com/todos/${id}`)
.pipe(catchError((err) => {
this.toastService.showError(err.message);
return EMPTY;
})
)
.subscribe(todo => this.todo = todo);
}
```
![http-request](http-request-5.gif)
- So are we going to add this **`catchError()`** operator to the entire http requests? **NO, we will use HTTP Interceptors!**
- Let's do it step by step.
### 1.Remove catchError pipe
```ts
getTodo(id: number) {
this.http.get('https://jsonplaceholder.typicode.com/todos/${id}').subscribe(todo => this.todo = todo);
}
```
### 2.Create an HTTP Interceptor
```ts
import { Injectable, inject } from '@angular/core';
import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { EMPTY, catchError } from 'rxjs';
import { ToastService } from './toast.service';
@Injectable({
providedIn: 'root'
})
export class ToastInterceptor implements HttpInterceptor {
toastService = inject(ToastService);
intercept(req: HttpRequest<any>, next: HttpHandler) {
return next.handle(req).pipe(catchError((error) => {
this.toastService.showError(error.message);
return EMPTY;
}));
}
}
```
### 3.Provide the interceptor
```ts
import { HTTP_INTERCEPTORS} from '@angular/common/http';
providers: [
{ provide: HTTP_INTERCEPTORS, useExisting: ToastInterceptor, multi: true }
]
```
Now everything has set up. Let's make an HTTP request and try again.
![http-request](http-request-5.gif)
- So, now we are handling http errors globally. Whenever an error occurs, it will be catched from interceptor and will be showed via toast message.
- But this method has a one little disadvantage. What if we dont want to show toast message for a spesific case? [related issue about this problem](https://github.com/angular/angular/issues/18155).
- **[ABP Framework](https://abp.io/) has great solution for this problem. Let's understand the solution and apply it straightforwardly.**
- Create a singleton service called **`HttpErrorReporterService`.** This service is going to store **HttpError** in a subject, and share the httpError as an observable for subscribers.
- Create a new service called **`RestService`** which is a layer on top of **`HttpClient`**, this new service is able to get a skipHandleError parameter. If skipHandleError value is **false** then it will be reported to the **`HttpErrorReporterService`** otherwise error will be throwed.
- Create a service called **`ErrorHandler`**, This service is going to subscribe to observable in **`HttpErrorReporterService`** and handle the errors (in our case we will show toast message).
- When i first see this solution, i loved it. For more information and detail you can check the source code from the links below;
- [**Http Error Reporter Service**](https://github.com/abpframework/abp/blob/360a3395aa0e44fb77574ea7eac745e8ba94b82e/npm/ng-packs/packages/core/src/lib/services/http-error-reporter.service.ts#L6)
- [**Rest Service**](https://github.com/abpframework/abp/blob/360a3395aa0e44fb77574ea7eac745e8ba94b82e/npm/ng-packs/packages/core/src/lib/services/rest.service.ts#L4)
- [**Error Handler**](https://github.com/abpframework/abp/blob/360a3395aa0e44fb77574ea7eac745e8ba94b82e/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts#L19)
You can copy the source codes from ABP or use ABP directly 😀
Lets simulate the solution in our application, this simulation is not suitable for your application it just for demonstration.
**Rest Service**
```ts
import { HttpClient, HttpRequest } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { HttpErrorReporterService } from './http-error-reporter.service';
import { catchError, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class RestService {
http = inject(HttpClient);
httpErrorReporterService = inject(HttpErrorReporterService);
request(req: HttpRequest<any> | { method: string, url: string; }, skipHandleError = false) {
const { method, url } = req;
return this.http.request(method, url).pipe(catchError((err) => {
if (!skipHandleError) {
this.httpErrorReporterService.reportError(err);
}
return throwError(() => err);
}));
}
}
```
**HttpErrorReporterService**
```ts
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class HttpErrorReporterService {
private _error$ = new Subject<HttpErrorResponse>();
get error$() {
return this._error$.asObservable();
}
reportError(error: HttpErrorResponse) {
this._error$.next(error);
}
}
```
**ErrorHandler**
```ts
import { Injectable, inject } from '@angular/core';
import { HttpErrorReporterService } from './http-error-reporter.service';
import { ToastService } from './toast.service';
@Injectable({
providedIn: 'root'
})
export class ErrorHandlerService {
httpErrorReporterService = inject(HttpErrorReporterService);
toastMessageService = inject(ToastService);
constructor(){
this.httpErrorReporterService.error$.subscribe((error) => {
this.toastMessageService.showError(error.message);
});
}
}
```
Now lets make an http request to check is it working
```ts
restService = inject(RestService);
getTodo() {
this.restService.request(
{ method: 'GET', url: 'https://jsonplaceholder.typicode.com/todos/1111' },
).subscribe(todo => {
this.todo = todo;
});
}
```
![http-request](http-request-7.gif)
Now let's pass **true to the `skipHandleError` parameter**, let's see if the **errorHandler** going to skip this error or not.
```ts
restService = inject(RestService);
getTodo() {
this.restService.request(
{ method: 'GET', url: '<https://jsonplaceholder.typicode.com/todos/1111>' },
skipHandleError: true,
).subscribe(todo => {
this.todo = todo;
});
}
```
![http-request](http-request-8.gif)
### Conclusion
- To handle synchronous errors globally, you can use [Error Handler](https://angular.io/api/core/ErrorHandler).
- To handle http errors globally, you can use [HTTP - interceptor](https://angular.io/guide/http-interceptor-use-cases). But in this method, you won't be able to skip specific cases. I recommend you use the ABP Framework's solution.
Thanks for reading, if you have any advice please share it with me in the comments.

BIN
docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error-image-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/error_handling.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-4.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-5.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-7.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/http-request-8.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
docs/en/Community-Articles/2024-02-12-Global-Error-Handling-in-Angular/show-toast-2.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Loading…
Cancel
Save