From 899910e9adb55eddbe3692be975fa8963b0b5909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20=C3=96zt=C3=BCrk?= Date: Wed, 17 Jan 2024 15:26:05 +0300 Subject: [PATCH 1/7] fix error handling, update documentation --- docs/en/UI/Angular/HTTP-Requests.md | 24 +++++++------- .../core/src/lib/services/rest.service.ts | 6 ++-- .../src/lib/handlers/error.handler.ts | 33 +++++++++---------- .../theme-shared/src/lib/models/common.ts | 4 +-- .../lib/providers/error-handlers.provider.ts | 2 +- .../src/lib/theme-shared.module.ts | 4 +-- 6 files changed, 36 insertions(+), 37 deletions(-) diff --git a/docs/en/UI/Angular/HTTP-Requests.md b/docs/en/UI/Angular/HTTP-Requests.md index a0e53df9c9..b524b4a8ee 100644 --- a/docs/en/UI/Angular/HTTP-Requests.md +++ b/docs/en/UI/Angular/HTTP-Requests.md @@ -219,23 +219,23 @@ 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 { throwError } from 'rxjs'; +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; + return EMPTY; } if (httpError.status === 404) { const contentProjection = injector.get(ContentProjectionService); contentProjection.projectContent(PROJECTION_STRATEGY.AppendComponentToBody(Error404Component)); - return; + return EMPTY; } - return throwError(httpError); + return of(httpError); } // app.module.ts @@ -267,22 +267,22 @@ In the example above: ![custom-error-handler-404-component](images/custom-error-handler-404-component.jpg) - - Since `throwError(httpError)` is returned at bottom of the `handleHttpErrors`, the `ErrorHandler` will handle the HTTP errors except 400 and 404 errors. + - 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` to next line of handling an error, default error handling will not work for that error. +**Note 1:** If you put `return EMPTY` to next line of handling an error, default error handling will not work for that error. `EMPTY` can be imported from `rxjs` ```js export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) { if (httpError.status === 403) { // handle 403 errors here - return; // put return to skip default error handling + return EMPTY; // put return to skip default error handling } } ``` -**Note 2:** If you put `return throwError(httpError)`, default error handling will work. - - `throwError` is a function. It can be imported from `rxjs`. +**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`. ```js @@ -291,11 +291,11 @@ import { throwError } from 'rxjs'; export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) { if (httpError.status === 500) { // handle 500 errors here - return; + return EMPTY; } - // you can return the throwError(httpError) at bottom of the function to run the default handler of ABP for HTTP errors that you didn't handle above. - return throwError(httpError) + // 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) } ``` diff --git a/npm/ng-packs/packages/core/src/lib/services/rest.service.ts b/npm/ng-packs/packages/core/src/lib/services/rest.service.ts index a7ee7a491b..83f91777ca 100644 --- a/npm/ng-packs/packages/core/src/lib/services/rest.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/rest.service.ts @@ -20,7 +20,7 @@ export class RestService { protected externalHttp: ExternalHttpClient, protected environment: EnvironmentService, protected httpErrorReporter: HttpErrorReporterService, - ) {} + ) { } protected getApiFromStore(apiName: string | undefined): string { return this.environment.getApiUrl(apiName); @@ -28,7 +28,7 @@ export class RestService { handleError(err: any): Observable { this.httpErrorReporter.reportError(err); - return throwError(err); + return throwError(() => err); } request( @@ -51,7 +51,7 @@ export class RestService { }), ...options, } as any) - .pipe(catchError(err => (skipHandleError ? throwError(err) : this.handleError(err)))); + .pipe(catchError(err => (skipHandleError ? throwError(() => err) : this.handleError(err)))); } private getHttpClient(isExternal: boolean) { return isExternal ? this.externalHttp : this.http; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts index 480a647bbe..a3417900a2 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts @@ -1,11 +1,11 @@ import { inject, Injectable, Injector } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; -import { Observable, of, throwError } from 'rxjs'; -import { catchError, filter, switchMap } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; +import { filter, switchMap } from 'rxjs/operators'; import { HttpErrorReporterService } from '@abp/ng.core'; -import { CustomHttpErrorHandlerService } from '../models/common'; +import { CustomHttpErrorHandlerService, HttpErrorHandler } from '../models/common'; import { Confirmation } from '../models/confirmation'; import { CUSTOM_ERROR_HANDLERS, HTTP_ERROR_HANDLER } from '../tokens/http-error.token'; @@ -21,10 +21,8 @@ export class ErrorHandler { protected readonly confirmationService = inject(ConfirmationService); protected readonly routerErrorHandlerService = inject(RouterErrorHandlerService); protected readonly httpErrorConfig = inject(HTTP_ERROR_CONFIG); - protected readonly customErrorHandlers = inject(CUSTOM_ERROR_HANDLERS); - protected readonly defaultHttpErrorHandler = (_, err: HttpErrorResponse) => throwError(() => err); - protected readonly httpErrorHandler = - inject(HTTP_ERROR_HANDLER, { optional: true }) || this.defaultHttpErrorHandler; + protected readonly defaultErrorHandlers = inject(CUSTOM_ERROR_HANDLERS); + protected readonly httpErrorHandler = inject(HTTP_ERROR_HANDLER, { optional: true }); constructor(protected injector: Injector) { this.listenToRestError(); @@ -42,10 +40,11 @@ export class ErrorHandler { } protected executeErrorHandler = (error: HttpErrorResponse) => { - const errHandler = this.httpErrorHandler(this.injector, error); - const isObservable = errHandler instanceof Observable; - - return (isObservable ? errHandler : of(null)).pipe(catchError(err => of(err))); + if (this.httpErrorHandler) { + return this.httpErrorHandler(this.injector, error); + } + + return of(error); }; protected sortHttpErrorHandlers( @@ -56,17 +55,17 @@ export class ErrorHandler { } protected handleError(err: unknown) { - if (this.customErrorHandlers && this.customErrorHandlers.length) { - const canHandleService = this.customErrorHandlers + if (this.defaultErrorHandlers && this.defaultErrorHandlers.length) { + const errorHandlerService = this.defaultErrorHandlers .sort(this.sortHttpErrorHandlers) .find(service => service.canHandle(err)); - - if (canHandleService) { - canHandleService.execute(); + + if (errorHandlerService) { + errorHandlerService.execute(); return; } } - + this.showError().subscribe(); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts index 82a14641ad..ac0e9ac120 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/models/common.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Type } from '@angular/core'; +import { Injector, Type } from '@angular/core'; import { Validation } from '@ngx-validate/core'; import { Observable } from 'rxjs'; import { ConfirmationIcons } from '../tokens/confirmation-icons.token'; @@ -20,7 +20,7 @@ export interface HttpErrorConfig { hideCloseIcon?: boolean; }; } -export type HttpErrorHandler = (httpError: HttpErrorResponse) => Observable; +export type HttpErrorHandler = (injector: Injector, httpError: HttpErrorResponse) => Observable; export type LocaleDirection = 'ltr' | 'rtl'; export interface CustomHttpErrorHandlerService { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts b/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts index 0d177ec510..61a2a25fcd 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts @@ -5,7 +5,7 @@ import { AbpFormatErrorHandlerService } from '../services/abp-format-error-handl import { StatusCodeErrorHandlerService } from '../services/status-code-error-handler.service'; import { UnknownStatusCodeErrorHandlerService } from '../services/unknown-status-code-error-handler.service'; -export const ERROR_HANDLERS_PROVIDERS: Provider[] = [ +export const DEFAULT_HANDLERS_PROVIDERS: Provider[] = [ { provide: CUSTOM_ERROR_HANDLERS, multi: true, diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 53c2498f60..d2d294bfc5 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -29,7 +29,7 @@ import { NgxDatatableListDirective } from './directives/ngx-datatable-list.direc import { DocumentDirHandlerService } from './handlers/document-dir.handler'; import { ErrorHandler } from './handlers/error.handler'; import { RootParams } from './models/common'; -import { ERROR_HANDLERS_PROVIDERS, NG_BOOTSTRAP_CONFIG_PROVIDERS } from './providers'; +import { DEFAULT_HANDLERS_PROVIDERS, NG_BOOTSTRAP_CONFIG_PROVIDERS } from './providers'; import { THEME_SHARED_ROUTE_PROVIDERS } from './providers/route.provider'; import { THEME_SHARED_APPEND_CONTENT } from './tokens/append-content.token'; import { HTTP_ERROR_CONFIG, httpErrorConfigFactory } from './tokens/http-error.token'; @@ -149,7 +149,7 @@ export class ThemeSharedModule { }, }, tenantNotFoundProvider, - ERROR_HANDLERS_PROVIDERS, + DEFAULT_HANDLERS_PROVIDERS, ], }; } From 2c37566528661e66fd814c837b47915cda4633ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20=C3=96zt=C3=BCrk?= Date: Fri, 19 Jan 2024 18:36:59 +0300 Subject: [PATCH 2/7] update documentation, deprecate HTTP_ERROR_HANDLER injection token --- docs/en/UI/Angular/HTTP-Error-Handling.md | 100 +++++++++++++ docs/en/UI/Angular/HTTP-Requests.md | 140 +----------------- docs/en/docs-nav.json | 15 +- .../src/lib/handlers/error.handler.ts | 12 +- .../lib/providers/error-handlers.provider.ts | 8 +- .../src/lib/tokens/http-error.token.ts | 3 + 6 files changed, 132 insertions(+), 146 deletions(-) create mode 100644 docs/en/UI/Angular/HTTP-Error-Handling.md diff --git a/docs/en/UI/Angular/HTTP-Error-Handling.md b/docs/en/UI/Angular/HTTP-Error-Handling.md new file mode 100644 index 0000000000..8bf2589204 --- /dev/null +++ b/docs/en/UI/Angular/HTTP-Error-Handling.md @@ -0,0 +1,100 @@ +# 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 + +A custom HTTP error handler service can be registered to an injection token named **`CUSTOM_ERROR_HANDLERS`**. ABP has some default error handlers, you can see them [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts). + +### How ABP handles errors and How to add new one? + +- First of all it will be better to understand how ABP handles errors. +- 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. You can think this as a z-index property in CSS. +- **`canHandle(error: unknown): boolean :`** Check if the service can handle the error. Returns boolean. +- **`execute(): void :`** If the service can handle the error, then run the execute method. + +**In Summarry** + +- 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; + private 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) + +**Note 1:** If your service cannot handle the error. Then ABP will check the next Error Service. +**Note 2:** If none of the service handle the error. Then basic confirmation message about the error will be shown to the user. +**Note 3:** You can provide more than one service, with CUSTOM_ERROR_HANDLER injection token. +**Note 4:** If you want your custom service to be evaluated (checked) earlier, set the priority variable high. diff --git a/docs/en/UI/Angular/HTTP-Requests.md b/docs/en/UI/Angular/HTTP-Requests.md index b524b4a8ee..c58e35fc51 100644 --- a/docs/en/UI/Angular/HTTP-Requests.md +++ b/docs/en/UI/Angular/HTTP-Requests.md @@ -1,7 +1,5 @@ # How to Make HTTP Requests - - ## About HttpClient Angular has the amazing [HttpClient](https://angular.io/guide/http) for communication with backend services. It is a layer on top and a simplified representation of [XMLHttpRequest Web API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). It also is the recommended agent by Angular for any HTTP request. There is nothing wrong with using the `HttpClient` in your ABP project. @@ -23,14 +21,10 @@ Although clear and flexible, handling errors this way is repetitive work, even w An `HttpInterceptor` is able to catch `HttpErrorResponse` and can be used for a centralized error handling. Nevertheless, cases where default error handler, therefore the interceptor, must be disabled require additional work and comprehension of Angular internals. Check [this issue](https://github.com/angular/angular/issues/20203) for details. - - ## RestService ABP core module has a utility service for HTTP requests: `RestService`. Unless explicitly configured otherwise, it catches HTTP errors and dispatches a `RestOccurError` action. This action is then captured by the `ErrorHandler` introduced by the `ThemeSharedModule`. Since you should already import this module in your app, when the `RestService` is used, all HTTP errors get automatically handled by default. - - ### Getting Started with RestService In order to use the `RestService`, you must inject it in your class as a dependency. @@ -48,11 +42,9 @@ class DemoService { You do not have to provide the `RestService` at module or component/directive level, because it is already **provided in root**. - - ### How to Make a Request with RestService -You can use the `request` method of the `RestService` is for HTTP requests. Here is an example: +You can use the `request` method of the `RestService` is for HTTP requests. Here is an example: ```js getFoo(id: number) { @@ -65,8 +57,6 @@ getFoo(id: number) { } ``` - - The `request` method always returns an `Observable`. Therefore you can do the following wherever you use `getFoo` method: ```js @@ -79,12 +69,8 @@ doSomethingWithFoo(id: number) { } ``` - - **You do not have to worry about unsubscription.** The `RestService` uses `HttpClient` behind the scenes, so every observable it returns is a finite observable, i.e. it closes subscriptions automatically upon success or error. - - As you see, `request` method gets a request options object with `Rest.Request` type. This generic type expects the interface of the request body. You may pass `null` when there is no body, like in a `GET` or a `DELETE` request. Here is an example where there is one: ```js @@ -99,11 +85,7 @@ postFoo(body: Foo) { } ``` - - -You may [check here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23) for complete `Rest.Request` type, which has only a few changes compared to [HttpRequest](https://angular.io/api/common/http/HttpRequest) class in Angular. - - +You may [check here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23) for complete `Rest.Request` type, which has only a few changes compared to [HttpRequest](https://angular.io/api/common/http/HttpRequest) class in Angular. ### How to Disable Default Error Handler of RestService @@ -120,8 +102,6 @@ deleteFoo(id: number) { } ``` - - `skipHandleError` config option, when set to `true`, disables the error handler and the returned observable starts throwing an error that you can catch in your subscription. ```js @@ -137,14 +117,10 @@ removeFooFromList(id: number) { } ``` - - ### How to Get a Specific API Endpoint From Application Config Another nice config option that `request` method receives is `apiName` (available as of v2.4), which can be used to get a specific module endpoint from application configuration. - - ```js putFoo(body: Foo, id: string) { const request: Rest.Request = { @@ -157,8 +133,6 @@ putFoo(body: Foo, id: string) { } ``` - - `putFoo` above will request `https://localhost:44305/api/some/path/to/foo/{id}` as long as the environment variables are as follows: ```js @@ -167,19 +141,17 @@ putFoo(body: Foo, id: string) { export const environment = { apis: { default: { - url: 'https://localhost:44305', + url: "https://localhost:44305", }, foo: { - url: 'https://localhost:44305/api/some/path/to/foo', + url: "https://localhost:44305/api/some/path/to/foo", }, }, - + /* rest of the environment variables here */ -} +}; ``` - - ### How to Observe Response Object or HTTP Events Instead of Body `RestService` assumes you are generally interested in the body of a response and, by default, sets `observe` property as `'body'`. However, there may be times you are rather interested in something else, such as a custom proprietary header. For that, the `request` method receives `observe` property in its config object. @@ -202,104 +174,6 @@ getSomeCustomHeaderValue() { You may find `Rest.Observe` enum [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10). - -## 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 - -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: - -```js -// 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, backend error message will be displayed as shown below: - - ![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg) - - - 404 not found errors is handled. When a 404 error occurs, `Error404Component` will be appended to the `` as shown below: - -![custom-error-handler-404-component](images/custom-error-handler-404-component.jpg) - - - 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` can be imported from `rxjs` - -```js -export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) { - if (httpError.status === 403) { - // handle 403 errors here - return EMPTY; // put return 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`. - -```js -import { throwError } from 'rxjs'; - -export function handleHttpErrors(injector: Injector, httpError: HttpErrorResponse) { - if (httpError.status === 500) { - // handle 500 errors here - return EMPTY; - } - - // 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) -} -``` - - ### How to Skip HTTP interceptors and ABP headers The ABP Framework adds several HTTP headers to the HttpClient, such as the "Auth token" or "tenant Id". @@ -309,4 +183,4 @@ The ABP Http interceptors check the value of the `IS_EXTERNAL_REQUEST` token. If The `ExternalHttpClient` extends from `HTTPClient` and sets the `IS_EXTERNAL_REQUEST` context token to true. When you are using `ExternalHttpClient` as HttpClient in your components, it does not add ABP-specific headers. -Note: With `IS_EXTERNAL_REQUEST` or without it, ABP loading service works. +Note: With `IS_EXTERNAL_REQUEST` or without it, ABP loading service works. diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 8e766c7430..be988b795c 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1053,7 +1053,16 @@ }, { "text": "HTTP Requests", - "path": "UI/Angular/HTTP-Requests.md" + "items": [ + { + "text": "How to Make HTTP Requests", + "path": "UI/Angular/HTTP-Requests.md" + }, + { + "text": "HTTP Error Handling / Customization", + "path": "UI/Angular/HTTP-Error-Handling.md" + } + ] }, { "text": "Localization", @@ -1173,8 +1182,8 @@ "path": "UI/Angular/Content-Security-Strategy.md" }, { - "text":"Abp Window Service", - "path":"UI/Angular/Abp-Window-Service.md" + "text": "Abp Window Service", + "path": "UI/Angular/Abp-Window-Service.md" } ] }, diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts index a3417900a2..168fef1f53 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts @@ -21,7 +21,7 @@ export class ErrorHandler { protected readonly confirmationService = inject(ConfirmationService); protected readonly routerErrorHandlerService = inject(RouterErrorHandlerService); protected readonly httpErrorConfig = inject(HTTP_ERROR_CONFIG); - protected readonly defaultErrorHandlers = inject(CUSTOM_ERROR_HANDLERS); + protected readonly customErrorHandlers = inject(CUSTOM_ERROR_HANDLERS); protected readonly httpErrorHandler = inject(HTTP_ERROR_HANDLER, { optional: true }); constructor(protected injector: Injector) { @@ -43,7 +43,7 @@ export class ErrorHandler { if (this.httpErrorHandler) { return this.httpErrorHandler(this.injector, error); } - + return of(error); }; @@ -55,17 +55,17 @@ export class ErrorHandler { } protected handleError(err: unknown) { - if (this.defaultErrorHandlers && this.defaultErrorHandlers.length) { - const errorHandlerService = this.defaultErrorHandlers + if (this.customErrorHandlers && this.customErrorHandlers.length) { + const errorHandlerService = this.customErrorHandlers .sort(this.sortHttpErrorHandlers) .find(service => service.canHandle(err)); - + if (errorHandlerService) { errorHandlerService.execute(); return; } } - + this.showError().subscribe(); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts b/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts index 61a2a25fcd..d456c82912 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts @@ -9,21 +9,21 @@ export const DEFAULT_HANDLERS_PROVIDERS: Provider[] = [ { provide: CUSTOM_ERROR_HANDLERS, multi: true, - useClass: TenantResolveErrorHandlerService, + useExisting: TenantResolveErrorHandlerService, }, { provide: CUSTOM_ERROR_HANDLERS, multi: true, - useClass: AbpFormatErrorHandlerService, + useExisting: AbpFormatErrorHandlerService, }, { provide: CUSTOM_ERROR_HANDLERS, multi: true, - useClass: StatusCodeErrorHandlerService, + useExisting: StatusCodeErrorHandlerService, }, { provide: CUSTOM_ERROR_HANDLERS, multi: true, - useClass: UnknownStatusCodeErrorHandlerService, + useExisting: UnknownStatusCodeErrorHandlerService, }, ]; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts b/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts index a2784f4332..64245680db 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tokens/http-error.token.ts @@ -15,6 +15,9 @@ export function httpErrorConfigFactory(config = {} as HttpErrorConfig) { export const HTTP_ERROR_CONFIG = new InjectionToken('HTTP_ERROR_CONFIG'); +/** + @deprecated use **`CUSTOM_ERROR_HANDLERS`** injection token instead of this, see more info https://docs.abp.io/en/abp/latest/UI/Angular/HTTP-Requests +*/ export const HTTP_ERROR_HANDLER = new InjectionToken('HTTP_ERROR_HANDLER'); export const CUSTOM_ERROR_HANDLERS = new InjectionToken( From 9546cdf0d02a0308835d9def1b76a8bfb0de4d99 Mon Sep 17 00:00:00 2001 From: masumulu28 Date: Tue, 23 Jan 2024 11:26:35 +0300 Subject: [PATCH 3/7] remove unused import --- .../packages/theme-shared/src/lib/handlers/error.handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts index 168fef1f53..30f9f7b3cb 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts @@ -5,7 +5,7 @@ import { filter, switchMap } from 'rxjs/operators'; import { HttpErrorReporterService } from '@abp/ng.core'; -import { CustomHttpErrorHandlerService, HttpErrorHandler } from '../models/common'; +import { CustomHttpErrorHandlerService } from '../models/common'; import { Confirmation } from '../models/confirmation'; import { CUSTOM_ERROR_HANDLERS, HTTP_ERROR_HANDLER } from '../tokens/http-error.token'; From af1cc0ffbae1a435c409bec1fe9259b39b02f5e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20=C3=96zt=C3=BCrk?= Date: Tue, 23 Jan 2024 14:39:54 +0300 Subject: [PATCH 4/7] update documentation --- docs/en/UI/Angular/HTTP-Error-Handling.md | 107 ++++++++++++++++++++-- docs/en/UI/Angular/HTTP-Requests.md | 4 + 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/docs/en/UI/Angular/HTTP-Error-Handling.md b/docs/en/UI/Angular/HTTP-Error-Handling.md index 8bf2589204..1b4de9c38a 100644 --- a/docs/en/UI/Angular/HTTP-Error-Handling.md +++ b/docs/en/UI/Angular/HTTP-Error-Handling.md @@ -4,11 +4,107 @@ When the `RestService` is used, all HTTP errors are reported to the [`HttpErrorR ## Custom HTTP Error Handler -A custom HTTP error handler service can be registered to an injection token named **`CUSTOM_ERROR_HANDLERS`**. ABP has some default error handlers, you can see them [here](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts). +### (Deprecated!) Function Method; -### How ABP handles errors and How to add new one? +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; + +With Services you can provide **more than one handler**. + +A custom HTTP error handler service can be registered to an injection token named **`CUSTOM_ERROR_HANDLERS`**. + +ABP has some default error handlers, you can see them [here](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? -- First of all it will be better to understand how ABP handles errors. - ABP error handler services are implements the interface of **CustomHttpErrorHandlerService**. **Interface of `CUSTOM_ERROR_HANDLERS`** @@ -25,7 +121,7 @@ interface CustomHttpErrorHandlerService { - **`canHandle(error: unknown): boolean :`** Check if the service can handle the error. Returns boolean. - **`execute(): void :`** If the service can handle the error, then run the execute method. -**In Summarry** +**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. @@ -45,9 +141,8 @@ import { ToasterService } from "@abp/ng.theme.shared"; export class MyCustomErrorHandlerService implements CustomHttpErrorHandlerService { - // You can write any number here, ex: 9999 - readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.veryHigh; + readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.veryHigh; private toaster = inject(ToasterService); private error: HttpErrorResponse | undefined = undefined; diff --git a/docs/en/UI/Angular/HTTP-Requests.md b/docs/en/UI/Angular/HTTP-Requests.md index c58e35fc51..9bef5cb8f2 100644 --- a/docs/en/UI/Angular/HTTP-Requests.md +++ b/docs/en/UI/Angular/HTTP-Requests.md @@ -184,3 +184,7 @@ The `ExternalHttpClient` extends from `HTTPClient` and sets the `IS_EXTERNAL_REQ When you are using `ExternalHttpClient` as HttpClient in your components, it does not add ABP-specific headers. Note: With `IS_EXTERNAL_REQUEST` or without it, ABP loading service works. + +## See Also + +- [HTTP Error Handling / Customization](./HTTP-Error-Handling) From 08adcc5c8c441e3896c9d04caacbf64e819b7920 Mon Sep 17 00:00:00 2001 From: Masum ULU <49063256+masumulu28@users.noreply.github.com> Date: Wed, 24 Jan 2024 08:58:40 +0300 Subject: [PATCH 5/7] Update HTTP-Error-Handling.md --- docs/en/UI/Angular/HTTP-Error-Handling.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/en/UI/Angular/HTTP-Error-Handling.md b/docs/en/UI/Angular/HTTP-Error-Handling.md index 1b4de9c38a..e657f5c773 100644 --- a/docs/en/UI/Angular/HTTP-Error-Handling.md +++ b/docs/en/UI/Angular/HTTP-Error-Handling.md @@ -138,12 +138,11 @@ 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 +export class MyCustomErrorHandlerService implements CustomHttpErrorHandlerService { // You can write any number here, ex: 9999 readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.veryHigh; - private toaster = inject(ToasterService); + 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. @@ -175,7 +174,11 @@ import { MyCustomErrorHandlerService } from './custom-error-handler.service'; // ... providers: [ // ... - { provide: CUSTOM_ERROR_HANDLERS, useExisting: MyCustomErrorHandlerService, multi: true } + { + provide: CUSTOM_ERROR_HANDLERS, + useExisting: MyCustomErrorHandlerService, + multi: true, + } ] }) export class AppModule {} From 9851f671b29da7fdbb7548e20f603c93a33a061b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20=C3=96zt=C3=BCrk?= Date: Wed, 24 Jan 2024 10:13:19 +0300 Subject: [PATCH 6/7] update document according to reviever comments --- docs/en/UI/Angular/HTTP-Error-Handling.md | 31 ++++++++++++----------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/en/UI/Angular/HTTP-Error-Handling.md b/docs/en/UI/Angular/HTTP-Error-Handling.md index e657f5c773..d2e873aa63 100644 --- a/docs/en/UI/Angular/HTTP-Error-Handling.md +++ b/docs/en/UI/Angular/HTTP-Error-Handling.md @@ -4,7 +4,7 @@ When the `RestService` is used, all HTTP errors are reported to the [`HttpErrorR ## Custom HTTP Error Handler -### (Deprecated!) Function Method; +### 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. @@ -95,17 +95,15 @@ export function handleHttpErrors( } ``` -### Service Method; +### Service Method With Services you can provide **more than one handler**. -A custom HTTP error handler service can be registered to an injection token named **`CUSTOM_ERROR_HANDLERS`**. +A custom HTTP error handler service can be registered to an 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). -ABP has some default error handlers, you can see them [here](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 -### How To Add New Handler Service? - -- ABP error handler services are implements the interface of **CustomHttpErrorHandlerService**. +ABP error handler services are implements the interface of **CustomHttpErrorHandlerService**. **Interface of `CUSTOM_ERROR_HANDLERS`** @@ -117,9 +115,9 @@ interface CustomHttpErrorHandlerService { } ``` -- **`priority`** ABP sorts the services according to the number of the priority variable. Higher priority will be checked first. You can think this as a z-index property in CSS. -- **`canHandle(error: unknown): boolean :`** Check if the service can handle the error. Returns boolean. -- **`execute(): void :`** If the service can handle the error, then run the execute method. +- **`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** @@ -138,7 +136,8 @@ 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 +export class MyCustomErrorHandlerService + implements CustomHttpErrorHandlerService { // You can write any number here, ex: 9999 readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.veryHigh; @@ -192,7 +191,9 @@ In the example above: ![custom-error-handler-toaster-message](images/custom-error-handler-toaster-message.jpg) -**Note 1:** If your service cannot handle the error. Then ABP will check the next Error Service. -**Note 2:** If none of the service handle the error. Then basic confirmation message about the error will be shown to the user. -**Note 3:** You can provide more than one service, with CUSTOM_ERROR_HANDLER injection token. -**Note 4:** If you want your custom service to be evaluated (checked) earlier, set the priority variable high. +### 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. From 20b7151d447e69aaa5944bddbc0842c5207fbd52 Mon Sep 17 00:00:00 2001 From: Masum ULU <49063256+masumulu28@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:45:37 +0300 Subject: [PATCH 7/7] Minor changes applied --- docs/en/UI/Angular/HTTP-Error-Handling.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/en/UI/Angular/HTTP-Error-Handling.md b/docs/en/UI/Angular/HTTP-Error-Handling.md index d2e873aa63..606211bfeb 100644 --- a/docs/en/UI/Angular/HTTP-Error-Handling.md +++ b/docs/en/UI/Angular/HTTP-Error-Handling.md @@ -97,9 +97,7 @@ export function handleHttpErrors( ### Service Method -With Services you can provide **more than one handler**. - -A custom HTTP error handler service can be registered to an 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). +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