-
- {{ title | abpLocalization }}
-
+
{{ statusText }} {{ title | abpLocalization }}
{{ details | abpLocalization }}
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/error/error.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/error/error.component.ts
index b86eb78c6b..7ccc5c97bd 100644
--- a/npm/ng-packs/packages/theme-shared/src/lib/components/error/error.component.ts
+++ b/npm/ng-packs/packages/theme-shared/src/lib/components/error/error.component.ts
@@ -1,23 +1,65 @@
-import { Component, Renderer2, ElementRef } from '@angular/core';
-import { Config } from '@abp/ng.core';
+import { Config, takeUntilDestroy } from '@abp/ng.core';
+import {
+ AfterViewInit,
+ Component,
+ ComponentFactoryResolver,
+ ElementRef,
+ EmbeddedViewRef,
+ OnDestroy,
+ Type,
+ ViewChild,
+} from '@angular/core';
+import { fromEvent, Subject } from 'rxjs';
+import { debounceTime, filter } from 'rxjs/operators';
@Component({
selector: 'abp-error',
templateUrl: './error.component.html',
styleUrls: ['error.component.scss'],
})
-export class ErrorComponent {
- title: string | Config.LocalizationWithDefault = 'Oops!';
+export class ErrorComponent implements AfterViewInit, OnDestroy {
+ cfRes: ComponentFactoryResolver;
- details: string | Config.LocalizationWithDefault = 'Sorry, an error has occured.';
+ status = 0;
- renderer: Renderer2;
+ title: Config.LocalizationParam = 'Oops!';
- elementRef: ElementRef;
+ details: Config.LocalizationParam = 'Sorry, an error has occured.';
- host: any;
+ customComponent: Type
= null;
+
+ destroy$: Subject;
+
+ @ViewChild('container', { static: false })
+ containerRef: ElementRef;
+
+ get statusText(): string {
+ return this.status ? `[${this.status}]` : '';
+ }
+
+ ngAfterViewInit() {
+ if (this.customComponent) {
+ const customComponentRef = this.cfRes.resolveComponentFactory(this.customComponent).create(null);
+ customComponentRef.instance.errorStatus = this.status;
+ this.containerRef.nativeElement.appendChild((customComponentRef.hostView as EmbeddedViewRef).rootNodes[0]);
+ customComponentRef.changeDetectorRef.detectChanges();
+ }
+
+ fromEvent(document, 'keyup')
+ .pipe(
+ takeUntilDestroy(this),
+ debounceTime(150),
+ filter((key: KeyboardEvent) => key && key.key === 'Escape'),
+ )
+ .subscribe(() => {
+ this.destroy();
+ });
+ }
+
+ ngOnDestroy() {}
destroy() {
- this.renderer.removeChild(this.host, this.elementRef.nativeElement);
+ this.destroy$.next();
+ this.destroy$.complete();
}
}
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 18efbc2b60..5f75b2ecd5 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,21 +1,25 @@
-import { RestOccurError } from '@abp/ng.core';
+import { Config, RestOccurError } from '@abp/ng.core';
import { HttpErrorResponse } from '@angular/common/http';
import {
ApplicationRef,
ComponentFactoryResolver,
EmbeddedViewRef,
+ Inject,
Injectable,
Injector,
- NgZone,
RendererFactory2,
+ Type,
+ ComponentRef,
} from '@angular/core';
-import { Router } from '@angular/router';
+import { Navigate, RouterError, RouterState, RouterDataResolved } from '@ngxs/router-plugin';
import { Actions, ofActionSuccessful, Store } from '@ngxs/store';
-import { Observable } from 'rxjs';
+import { Observable, Subject } from 'rxjs';
import snq from 'snq';
import { ErrorComponent } from '../components/error/error.component';
+import { HttpErrorConfig, ErrorScreenErrorCodes } from '../models/common';
import { Toaster } from '../models/toaster';
import { ConfirmationService } from '../services/confirmation.service';
+import { HTTP_ERROR_CONFIG } from '../tokens/error-pages.token';
export const DEFAULT_ERROR_MESSAGES = {
defaultError: {
@@ -36,82 +40,142 @@ export const DEFAULT_ERROR_MESSAGES = {
},
defaultError500: {
title: '500',
- details: { key: 'AbpAccount::InternalServerErrorMessage', defaultValue: 'Error detail not sent by server.' },
- },
- defaultErrorUnknown: {
- title: 'Unknown Error',
- details: { key: 'AbpAccount::InternalServerErrorMessage', defaultValue: 'Error detail not sent by server.' },
+ details: 'Error detail not sent by server.',
},
};
@Injectable({ providedIn: 'root' })
export class ErrorHandler {
+ componentRef: ComponentRef;
+
constructor(
private actions: Actions,
- private router: Router,
- private ngZone: NgZone,
private store: Store,
private confirmationService: ConfirmationService,
private appRef: ApplicationRef,
private cfRes: ComponentFactoryResolver,
private rendererFactory: RendererFactory2,
private injector: Injector,
+ @Inject(HTTP_ERROR_CONFIG) private httpErrorConfig: HttpErrorConfig,
) {
- actions.pipe(ofActionSuccessful(RestOccurError)).subscribe(res => {
- const { payload: err = {} as HttpErrorResponse | any } = res;
- const body = snq(() => (err as HttpErrorResponse).error.error, DEFAULT_ERROR_MESSAGES.defaultError.title);
+ this.actions.pipe(ofActionSuccessful(RestOccurError, RouterError, RouterDataResolved)).subscribe(res => {
+ if (res instanceof RestOccurError) {
+ const { payload: err = {} as HttpErrorResponse | any } = res;
+ const body = snq(() => (err as HttpErrorResponse).error.error, DEFAULT_ERROR_MESSAGES.defaultError.title);
- if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) {
- const confirmation$ = this.showError(null, null, body);
+ if (err instanceof HttpErrorResponse && err.headers.get('_AbpErrorFormat')) {
+ const confirmation$ = this.showError(null, null, body);
- if (err.status === 401) {
- confirmation$.subscribe(() => {
- this.navigateToLogin();
- });
- }
- } else {
- switch ((err as HttpErrorResponse).status) {
- case 401:
- this.showError(
- DEFAULT_ERROR_MESSAGES.defaultError401.details,
- DEFAULT_ERROR_MESSAGES.defaultError401.title,
- ).subscribe(() => this.navigateToLogin());
- break;
- case 403:
- this.createErrorComponent({
- title: DEFAULT_ERROR_MESSAGES.defaultError403.title,
- details: DEFAULT_ERROR_MESSAGES.defaultError403.details,
+ if (err.status === 401) {
+ confirmation$.subscribe(() => {
+ this.navigateToLogin();
});
- break;
- case 404:
- this.showError(
- DEFAULT_ERROR_MESSAGES.defaultError404.details,
- DEFAULT_ERROR_MESSAGES.defaultError404.title,
- );
- break;
- case 500:
- this.createErrorComponent({
- title: DEFAULT_ERROR_MESSAGES.defaultError500.title,
- details: DEFAULT_ERROR_MESSAGES.defaultError500.details,
- });
- break;
- case 0:
- if ((err as HttpErrorResponse).statusText === 'Unknown Error') {
+ }
+ } else {
+ switch ((err as HttpErrorResponse).status) {
+ case 401:
+ this.canCreateCustomError(401)
+ ? this.show401Page()
+ : this.showError(
+ {
+ key: 'AbpAccount::DefaultErrorMessage401',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title,
+ },
+ {
+ key: 'AbpAccount::DefaultErrorMessage401Detail',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.details,
+ },
+ ).subscribe(() => this.navigateToLogin());
+ break;
+ case 403:
+ this.createErrorComponent({
+ title: {
+ key: 'AbpAccount::DefaultErrorMessage403',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.title,
+ },
+ details: {
+ key: 'AbpAccount::DefaultErrorMessage403Detail',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError403.details,
+ },
+ status: 403,
+ });
+ break;
+ case 404:
+ this.canCreateCustomError(404)
+ ? this.show404Page()
+ : this.showError(
+ {
+ key: 'AbpAccount::DefaultErrorMessage404',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title,
+ },
+ {
+ key: 'AbpAccount::DefaultErrorMessage404Detail',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.details,
+ },
+ );
+ break;
+ case 500:
this.createErrorComponent({
- title: DEFAULT_ERROR_MESSAGES.defaultErrorUnknown.title,
- details: DEFAULT_ERROR_MESSAGES.defaultErrorUnknown.details,
+ title: {
+ key: 'AbpAccount::500Message',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.title,
+ },
+ details: {
+ key: 'AbpAccount::InternalServerErrorMessage',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError500.details,
+ },
+ status: 500,
});
- }
- break;
- default:
- this.showError(DEFAULT_ERROR_MESSAGES.defaultError.details, DEFAULT_ERROR_MESSAGES.defaultError.title);
- break;
+ break;
+ case 0:
+ if ((err as HttpErrorResponse).statusText === 'Unknown Error') {
+ this.createErrorComponent({
+ title: {
+ key: 'AbpAccount::DefaultErrorMessage',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError.title,
+ },
+ });
+ }
+ break;
+ default:
+ this.showError(DEFAULT_ERROR_MESSAGES.defaultError.details, DEFAULT_ERROR_MESSAGES.defaultError.title);
+ break;
+ }
}
+ } else if (res instanceof RouterError && snq(() => res.event.error.indexOf('Cannot match') > -1, false)) {
+ this.show404Page();
+ } else if (res instanceof RouterDataResolved && this.componentRef) {
+ this.componentRef.destroy();
+ this.componentRef = null;
}
});
}
- private showError(message?: string, title?: string, body?: any): Observable {
+ private show401Page() {
+ this.createErrorComponent({
+ title: {
+ key: 'AbpAccount::401Message',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError401.title,
+ },
+ status: 401,
+ });
+ }
+
+ private show404Page() {
+ this.createErrorComponent({
+ title: {
+ key: 'AbpAccount::404Message',
+ defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title,
+ },
+ status: 404,
+ });
+ }
+
+ private showError(
+ message?: Config.LocalizationParam,
+ title?: Config.LocalizationParam,
+ body?: any,
+ ): Observable {
if (body) {
if (body.details) {
message = body.details;
@@ -123,35 +187,50 @@ export class ErrorHandler {
return this.confirmationService.error(message, title, {
hideCancelBtn: true,
- yesCopy: 'OK',
+ yesText: 'AbpAccount::Close',
});
}
private navigateToLogin() {
- this.ngZone.run(() => {
- this.router.navigate(['/account/login'], {
- state: { redirectUrl: this.router.url },
- });
- });
+ console.warn(this.store.selectSnapshot(RouterState.url));
+ this.store.dispatch(
+ new Navigate(['/account/login'], null, { state: { redirectUrl: this.store.selectSnapshot(RouterState.url) } }),
+ );
}
createErrorComponent(instance: Partial) {
const renderer = this.rendererFactory.createRenderer(null, null);
const host = renderer.selectRootElement(document.body, true);
- const componentRef = this.cfRes.resolveComponentFactory(ErrorComponent).create(this.injector);
+ this.componentRef = this.cfRes.resolveComponentFactory(ErrorComponent).create(this.injector);
- for (const key in componentRef.instance) {
- if (componentRef.instance.hasOwnProperty(key)) {
- componentRef.instance[key] = instance[key];
+ for (const key in this.componentRef.instance) {
+ if (this.componentRef.instance.hasOwnProperty(key)) {
+ this.componentRef.instance[key] = instance[key];
}
}
- this.appRef.attachView(componentRef.hostView);
- renderer.appendChild(host, (componentRef.hostView as EmbeddedViewRef).rootNodes[0]);
+ if (this.canCreateCustomError(instance.status as ErrorScreenErrorCodes)) {
+ this.componentRef.instance.cfRes = this.cfRes;
+ this.componentRef.instance.customComponent = this.httpErrorConfig.errorScreen.component;
+ }
+
+ this.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;
+ });
+ }
- componentRef.instance.renderer = renderer;
- componentRef.instance.elementRef = componentRef.location;
- componentRef.instance.host = host;
+ canCreateCustomError(status: ErrorScreenErrorCodes): boolean {
+ return snq(
+ () =>
+ this.httpErrorConfig.errorScreen.component &&
+ this.httpErrorConfig.errorScreen.forWhichErrors.indexOf(status) > -1,
+ );
}
}