Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix / Enhancement For Error Handler, Update Documentation #18787

Merged
merged 7 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
197 changes: 197 additions & 0 deletions docs/en/UI/Angular/HTTP-Error-Handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# HTTP Error Handling

When the `RestService` is used, all HTTP errors are reported to the [`HttpErrorReporterService`](./HTTP-Error-Reporter-Service), and then `ErrorHandler`, a service exposed by the `@abp/ng.theme.shared` package automatically handles the errors.

## Custom HTTP Error Handler

### Function Method `Deprecated`

A custom HTTP error handler can be registered to an injection token named `HTTP_ERROR_HANDLER`. If a custom handler function is registered, the `ErrorHandler` executes that function.

See an example:

```ts
// http-error-handler.ts
import { ContentProjectionService, PROJECTION_STRATEGY } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { HttpErrorResponse } from '@angular/common/http';
import { Injector } from '@angular/core';
import { of, EMPTY } from 'rxjs';
import { Error404Component } from './error404/error404.component';

export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) {
if (httpError.status === 400) {
const toaster = injector.get(ToasterService);
toaster.error(httpError.error?.error?.message || 'Bad request!', '400');
return EMPTY;
}

if (httpError.status === 404) {
const contentProjection = injector.get(ContentProjectionService);
contentProjection.projectContent(PROJECTION_STRATEGY.AppendComponentToBody(Error404Component));
return EMPTY;
}

return of(httpError);
}

// app.module.ts
import { Error404Component } from './error404/error404.component';
import { handleHttpErrors } from './http-error-handling';
import { HTTP_ERROR_HANDLER, ... } from '@abp/ng.theme.shared';

@NgModule({
// ...
providers: [
// ...
{ provide: HTTP_ERROR_HANDLER, useValue: handleHttpErrors }
],
declarations: [
//...
Error404Component],
})
export class AppModule {}
```

In the example above:

- Created a function named `handleHttpErrors` and defined as value of the `HTTP_ERROR_HANDLER` provider in app.module. After this, the function executes when an HTTP error occurs.
- 400 bad request errors is handled. When a 400 error occurs.

- Since `of(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors.

**Note 1:** If you put `return EMPTY` to next line of handling an error, default error handling will not work for that error. [EMPTY](https://rxjs.dev/api/index/const/EMPTY) can be imported from `rxjs`.

```ts
export function handleHttpErrors(
injector: Injector,
httpError: HttpErrorResponse
) {
if (httpError.status === 403) {
// handle 403 errors here
return EMPTY; // put return EMPTY to skip default error handling
}
}
```

**Note 2:** If you put `return of(httpError)`, default error handling will work.

- `of` is a function. It can be imported from `rxjs`.
- `httpError` is the second parameter of the error handler function which is registered to the `HTTP_ERROR_HANDLER` provider. Type of the `httpError` is `HttpErrorResponse`.

```ts
import { of } from "rxjs";

export function handleHttpErrors(
injector: Injector,
httpError: HttpErrorResponse
) {
if (httpError.status === 500) {
// handle 500 errors here
}

// you can return the of(httpError) at bottom of the function to run the default handler of ABP for HTTP errors that you didn't handle above.
return of(httpError);
}
```

### Service Method

You can provide **more than one handler** with services, a custom HTTP error handler service can be registered with injection token named **`CUSTOM_ERROR_HANDLERS`**. ABP has some default [error handlers](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts).

### How To Add New Handler Service

ABP error handler services are implements the interface of **CustomHttpErrorHandlerService**.

**Interface of `CUSTOM_ERROR_HANDLERS`**

```ts
interface CustomHttpErrorHandlerService {
readonly priority: number;
canHandle(error: unknown): boolean;
execute(): void;
}
```

- **`priority`** ABP sorts the services according to the number of the priority variable. Higher priority will be checked first.
- **`canHandle`** Check if the service can handle the error. Returns boolean.
- **`execute`** If the service can handle the error, then run the execute method.

**In Summary**

- Services are sorted by their priority number.
- Start from highest priority service and run canHandle() method. Pick the service if can handle the error, if not check next service.
- If the service found, run the execute method of a service. Done.

See an example:

```ts
// custom-error-handler.service.ts
import { inject, Injectable } from "@angular/core";
import { HttpErrorResponse } from "@angular/common/http";
import { CustomHttpErrorHandlerService } from "@abp/ng.theme.shared";
import { CUSTOM_HTTP_ERROR_HANDLER_PRIORITY } from "@abp/ng.theme.shared";
import { ToasterService } from "@abp/ng.theme.shared";

@Injectable({ providedIn: "root" })
export class MyCustomErrorHandlerService
implements CustomHttpErrorHandlerService
{
// You can write any number here, ex: 9999
readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.veryHigh;
protected readonly toaster = inject(ToasterService);
private error: HttpErrorResponse | undefined = undefined;

// What kind of error should be handled by this service? You can decide it in this method. If error is suitable to your case then return true; otherwise return false.
canHandle(error: unknown): boolean {
if (error instanceof HttpErrorResponse && error.status === 400) {
this.error = error;
return true;
}
return false;
}

// If this service is picked from ErrorHandler, this execute method will be called.
execute() {
this.toaster.error(
this.error.error?.error?.message || "Bad request!",
"400"
);
}
}
```

```ts

// app.module.ts
import { CUSTOM_ERROR_HANDLERS, ... } from '@abp/ng.theme.shared';
import { MyCustomErrorHandlerService } from './custom-error-handler.service';

@NgModule({
// ...
providers: [
// ...
{
provide: CUSTOM_ERROR_HANDLERS,
useExisting: MyCustomErrorHandlerService,
multi: true,
}
]
})
export class AppModule {}
```

In the example above:

- Created a service named `MyCustomErrorHandlerService`, and provided via `useExisting` key because we dont want another instance of it. And set `multi` key to true because ABP default error handlers are also provided with **CUSTOM_ERROR_HANDLERS** injection token.

- 400 errors are handled from custom `MyCustomErrorHandlerService`. When a 400 error occurs, backend error message will be displayed as shown below:

![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg)

### Notes

- If your service cannot handle the error. Then ABP will check the next Error Service.
- If none of the service handle the error. Then basic confirmation message about the error will be shown to the user.
- You can provide more than one service, with CUSTOM_ERROR_HANDLER injection token.
- If you want your custom service to be evaluated (checked) earlier, set the priority variable high.