diff --git a/npm/ng-packs/packages/theme-shared/src/lib/constants/error.ts b/npm/ng-packs/packages/theme-shared/src/lib/constants/error.ts new file mode 100644 index 0000000000..9ed1c93202 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/constants/error.ts @@ -0,0 +1,45 @@ +export const DEFAULT_ERROR_MESSAGES = { + defaultError: { + title: 'An error has occurred!', + details: 'Error detail not sent by server.', + }, + defaultError401: { + title: 'You are not authenticated!', + details: 'You should be authenticated (sign in) in order to perform this operation.', + }, + defaultError403: { + title: 'You are not authorized!', + details: 'You are not allowed to perform this operation.', + }, + defaultError404: { + title: 'Resource not found!', + details: 'The resource requested could not found on the server.', + }, + defaultError500: { + title: 'Internal server error', + details: 'Error detail not sent by server.', + }, +}; + +export const DEFAULT_ERROR_LOCALIZATIONS = { + defaultError: { + title: 'AbpUi::DefaultErrorMessage', + details: 'AbpUi::DefaultErrorMessageDetail', + }, + defaultError401: { + title: 'AbpUi::DefaultErrorMessage401', + details: 'AbpUi::DefaultErrorMessage401Detail', + }, + defaultError403: { + title: 'AbpUi::DefaultErrorMessage403', + details: 'AbpUi::DefaultErrorMessage403Detail', + }, + defaultError404: { + title: 'AbpUi::DefaultErrorMessage404', + details: 'AbpUi::DefaultErrorMessage404Detail', + }, + defaultError500: { + title: 'AbpUi::500Message', + details: 'AbpUi::DefaultErrorMessage', + }, +}; 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 cc7f733798..7a2736be78 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 @@ -7,10 +7,9 @@ } from '@abp/ng.core'; import { HttpErrorResponse } from '@angular/common/http'; import { - ApplicationRef, ComponentFactoryResolver, ComponentRef, - EmbeddedViewRef, + inject, Injectable, Injector, RendererFactory2, @@ -23,57 +22,12 @@ import { ErrorScreenErrorCodes, HttpErrorConfig } from '../models/common'; import { Confirmation } from '../models/confirmation'; import { ConfirmationService } from '../services/confirmation.service'; import { HTTP_ERROR_HANDLER } from '../tokens/http-error.token'; - -export const DEFAULT_ERROR_MESSAGES = { - defaultError: { - title: 'An error has occurred!', - details: 'Error detail not sent by server.', - }, - defaultError401: { - title: 'You are not authenticated!', - details: 'You should be authenticated (sign in) in order to perform this operation.', - }, - defaultError403: { - title: 'You are not authorized!', - details: 'You are not allowed to perform this operation.', - }, - defaultError404: { - title: 'Resource not found!', - details: 'The resource requested could not found on the server.', - }, - defaultError500: { - title: 'Internal server error', - details: 'Error detail not sent by server.', - }, -}; - -export const DEFAULT_ERROR_LOCALIZATIONS = { - defaultError: { - title: 'AbpUi::DefaultErrorMessage', - details: 'AbpUi::DefaultErrorMessageDetail', - }, - defaultError401: { - title: 'AbpUi::DefaultErrorMessage401', - details: 'AbpUi::DefaultErrorMessage401Detail', - }, - defaultError403: { - title: 'AbpUi::DefaultErrorMessage403', - details: 'AbpUi::DefaultErrorMessage403Detail', - }, - defaultError404: { - title: 'AbpUi::DefaultErrorMessage404', - details: 'AbpUi::DefaultErrorMessage404Detail', - }, - defaultError500: { - title: 'AbpUi::500Message', - details: 'AbpUi::DefaultErrorMessage', - }, -}; +import { CreateErrorComponentService } from '../services/create-error-component.service'; +import { CanCreateCustomErrorService } from '../services/can-create-custom-error.service'; +import { DEFAULT_ERROR_LOCALIZATIONS, DEFAULT_ERROR_MESSAGES } from '../constants/error'; @Injectable({ providedIn: 'root' }) export class ErrorHandler { - componentRef: ComponentRef | null = null; - protected httpErrorHandler = this.injector.get(HTTP_ERROR_HANDLER, (_, err: HttpErrorResponse) => throwError(err), ); @@ -88,6 +42,8 @@ export class ErrorHandler { private authService: AuthService; + private createErrorComponentService = inject(CreateErrorComponentService); + private canCreateCustomErrorService = inject(CanCreateCustomErrorService); constructor(protected injector: Injector) { this.httpErrorReporter = injector.get(HttpErrorReporterService); this.routerEvents = injector.get(RouterEvents); @@ -100,7 +56,6 @@ export class ErrorHandler { this.listenToRestError(); this.listenToRouterError(); - this.listenToRouterDataResolved(); } protected listenToRouterError() { @@ -110,16 +65,6 @@ export class ErrorHandler { .subscribe(() => this.show404Page()); } - protected listenToRouterDataResolved() { - this.routerEvents - .getEvents(ResolveEnd) - .pipe(filter(() => !!this.componentRef)) - .subscribe(() => { - this.componentRef?.destroy(); - this.componentRef = null; - }); - } - protected listenToRestError() { this.httpErrorReporter.reporter$ .pipe(filter(this.filterRestErrors), switchMap(this.executeErrorHandler)) @@ -151,7 +96,8 @@ export class ErrorHandler { return; } - if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) { + const isAbpErrorFormat = err.headers.get('_AbpErrorFormat'); + if (err instanceof HttpErrorResponse && isAbpErrorFormat) { const confirmation$ = this.showErrorWithRequestBody(body); if (err.status === 401) { @@ -302,47 +248,11 @@ export class ErrorHandler { } createErrorComponent(instance: Partial) { - const renderer = this.rendererFactory.createRenderer(null, null); - const host = renderer.selectRootElement(document.body, true); - - this.componentRef = this.cfRes - .resolveComponentFactory(HttpErrorWrapperComponent) - .create(this.injector); - - for (const key in instance) { - /* istanbul ignore else */ - if (Object.prototype.hasOwnProperty.call(this.componentRef.instance, key)) { - (this.componentRef.instance as any)[key] = (instance as any)[key]; - } - } - - this.componentRef.instance.hideCloseIcon = !!this.httpErrorConfig.errorScreen?.hideCloseIcon; - const appRef = this.injector.get(ApplicationRef); - - if (this.canCreateCustomError(instance.status as ErrorScreenErrorCodes)) { - this.componentRef.instance.cfRes = this.cfRes; - this.componentRef.instance.appRef = appRef; - this.componentRef.instance.injector = this.injector; - this.componentRef.instance.customComponent = this.httpErrorConfig.errorScreen?.component; - } - - appRef.attachView(this.componentRef.hostView); - renderer.appendChild(host, (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0]); - - const destroy$ = new Subject(); - this.componentRef.instance.destroy$ = destroy$; - destroy$.subscribe(() => { - this.componentRef?.destroy(); - this.componentRef = null; - }); + this.createErrorComponentService.execute(instance); } canCreateCustomError(status: ErrorScreenErrorCodes): boolean { - return !!( - this.httpErrorConfig?.errorScreen?.component && - this.httpErrorConfig?.errorScreen?.forWhichErrors && - this.httpErrorConfig?.errorScreen?.forWhichErrors.indexOf(status) > -1 - ); + return this.canCreateCustomErrorService.execute(status); } protected filterRestErrors = ({ status }: HttpErrorResponse): boolean => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/can-create-custom-error.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/can-create-custom-error.service.ts new file mode 100644 index 0000000000..7ce1a39262 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/can-create-custom-error.service.ts @@ -0,0 +1,18 @@ +import { inject, Injectable } from '@angular/core'; +import { ErrorScreenErrorCodes } from '../models/common'; +import { HTTP_ERROR_CONFIG } from '@abp/ng.theme.shared'; + +@Injectable({ + providedIn: 'root', +}) +export class CanCreateCustomErrorService { + private httpErrorConfig = inject(HTTP_ERROR_CONFIG); + + execute(status: ErrorScreenErrorCodes): boolean { + return !!( + this.httpErrorConfig?.errorScreen?.component && + this.httpErrorConfig?.errorScreen?.forWhichErrors && + this.httpErrorConfig?.errorScreen?.forWhichErrors.indexOf(status) > -1 + ); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts b/npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts new file mode 100644 index 0000000000..1ca37d2073 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts @@ -0,0 +1,89 @@ +import { + ApplicationRef, + ComponentFactoryResolver, + ComponentRef, + EmbeddedViewRef, + inject, + Injectable, + Injector, + RendererFactory2, +} from '@angular/core'; +import { + ErrorScreenErrorCodes, + HTTP_ERROR_CONFIG, + HttpErrorWrapperComponent, +} from '@abp/ng.theme.shared'; +import { Subject } from 'rxjs'; +import { ResolveEnd } from '@angular/router'; +import { filter } from 'rxjs/operators'; +import { RouterEvents } from '@abp/ng.core'; +import { CanCreateCustomErrorService } from './can-create-custom-error.service'; + +@Injectable({ providedIn: 'root' }) +export class CreateErrorComponentService { + protected rendererFactory = inject(RendererFactory2); + protected cfRes = inject(ComponentFactoryResolver); + private routerEvents = inject(RouterEvents); + private injector = inject(Injector); + private canCreateCustomErrorService = inject(CanCreateCustomErrorService); + private httpErrorConfig = inject(HTTP_ERROR_CONFIG); + + componentRef: ComponentRef | null = null; + + protected getErrorHostElement() { + return document.body; + } + + constructor() { + this.listenToRouterDataResolved(); + } + protected listenToRouterDataResolved() { + this.routerEvents + .getEvents(ResolveEnd) + .pipe(filter(() => !!this.componentRef)) + .subscribe(() => { + this.componentRef?.destroy(); + this.componentRef = null; + }); + } + + private isCloseIconHidden() { + return !!this.httpErrorConfig.errorScreen?.hideCloseIcon; + } + execute(instance: Partial) { + const renderer = this.rendererFactory.createRenderer(null, null); + const hostElement = this.getErrorHostElement(); + const host = renderer.selectRootElement(hostElement, true); + + this.componentRef = this.cfRes + .resolveComponentFactory(HttpErrorWrapperComponent) + .create(this.injector); + + for (const key in instance) { + /* istanbul ignore else */ + if (Object.prototype.hasOwnProperty.call(this.componentRef.instance, key)) { + (this.componentRef.instance as any)[key] = (instance as any)[key]; + } + } + + this.componentRef.instance.hideCloseIcon = this.isCloseIconHidden(); + const appRef = this.injector.get(ApplicationRef); + + if (this.canCreateCustomErrorService.execute(instance.status as ErrorScreenErrorCodes)) { + this.componentRef.instance.cfRes = this.cfRes; + this.componentRef.instance.appRef = appRef; + this.componentRef.instance.injector = this.injector; + this.componentRef.instance.customComponent = this.httpErrorConfig.errorScreen?.component; + } + + appRef.attachView(this.componentRef.hostView); + renderer.appendChild(host, (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0]); + + const destroy$ = new Subject(); + this.componentRef.instance.destroy$ = destroy$; + destroy$.subscribe(() => { + this.componentRef?.destroy(); + this.componentRef = null; + }); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/public-api.ts b/npm/ng-packs/packages/theme-shared/src/public-api.ts index 56cd9936a7..840463a70f 100644 --- a/npm/ng-packs/packages/theme-shared/src/public-api.ts +++ b/npm/ng-packs/packages/theme-shared/src/public-api.ts @@ -5,6 +5,7 @@ export * from './lib/animations'; export * from './lib/components'; export * from './lib/constants/validation'; +export * from './lib/constants/error'; export * from './lib/directives'; export * from './lib/enums'; export * from './lib/handlers';