Browse Source

Merge pull request #17504 from abpframework/auto-merge/rel-7-4/2152

Merge branch dev with rel-7.4
pull/17523/head
Masum ULU 3 years ago
committed by GitHub
parent
commit
ec1967a6b7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts
  2. 44
      npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts
  3. 6
      npm/ng-packs/packages/theme-shared/src/lib/models/common.ts
  4. 48
      npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts
  5. 29
      npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts
  6. 88
      npm/ng-packs/packages/theme-shared/src/lib/services/status-code-error-handler.service.ts

32
npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts

@ -1,19 +1,22 @@
import { LocalizationParam, SubscriptionService } from '@abp/ng.core';
import { import {
AfterViewInit,
ApplicationRef, ApplicationRef,
Component, Component,
Injector,
inject,
OnInit,
ComponentFactoryResolver, ComponentFactoryResolver,
ElementRef, ElementRef,
EmbeddedViewRef, EmbeddedViewRef,
Injector,
OnDestroy,
OnInit,
Type, Type,
ViewChild, ViewChild,
AfterViewInit,
OnDestroy,
} from '@angular/core'; } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { fromEvent, Subject } from 'rxjs'; import { fromEvent, Subject } from 'rxjs';
import { debounceTime, filter } from 'rxjs/operators'; import { debounceTime, filter } from 'rxjs/operators';
import { LocalizationParam, SubscriptionService } from '@abp/ng.core';
import { ErrorScreenErrorCodes } from '../../models';
@Component({ @Component({
selector: 'abp-http-error-wrapper', selector: 'abp-http-error-wrapper',
@ -21,14 +24,17 @@ import { debounceTime, filter } from 'rxjs/operators';
styleUrls: ['http-error-wrapper.component.scss'], styleUrls: ['http-error-wrapper.component.scss'],
providers: [SubscriptionService], providers: [SubscriptionService],
}) })
export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnInit { export class HttpErrorWrapperComponent implements OnInit, AfterViewInit, OnDestroy {
protected readonly document = inject(DOCUMENT);
protected readonly window = this.document.defaultView;
appRef!: ApplicationRef; appRef!: ApplicationRef;
cfRes!: ComponentFactoryResolver; cfRes!: ComponentFactoryResolver;
injector!: Injector; injector!: Injector;
status = 0; status: ErrorScreenErrorCodes = 0;
title: LocalizationParam = 'Oops!'; title: LocalizationParam = 'Oops!';
@ -53,12 +59,12 @@ export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnIn
constructor(private subscription: SubscriptionService) {} constructor(private subscription: SubscriptionService) {}
ngOnInit() { ngOnInit(): void {
this.backgroundColor = this.backgroundColor =
window.getComputedStyle(document.body)?.getPropertyValue('background-color') || '#fff'; this.window.getComputedStyle(this.document.body)?.getPropertyValue('background-color') || '#fff';
} }
ngAfterViewInit() { ngAfterViewInit(): void {
if (this.customComponent) { if (this.customComponent) {
const customComponentRef = this.cfRes const customComponentRef = this.cfRes
.resolveComponentFactory(this.customComponent) .resolveComponentFactory(this.customComponent)
@ -74,18 +80,18 @@ export class HttpErrorWrapperComponent implements AfterViewInit, OnDestroy, OnIn
customComponentRef.changeDetectorRef.detectChanges(); customComponentRef.changeDetectorRef.detectChanges();
} }
const keyup$ = fromEvent<KeyboardEvent>(document, 'keyup').pipe( const keyup$ = fromEvent<KeyboardEvent>(this.document, 'keyup').pipe(
debounceTime(150), debounceTime(150),
filter((key: KeyboardEvent) => key && key.key === 'Escape'), filter((key: KeyboardEvent) => key && key.key === 'Escape'),
); );
this.subscription.addOne(keyup$, () => this.destroy()); this.subscription.addOne(keyup$, () => this.destroy());
} }
ngOnDestroy() { ngOnDestroy(): void {
this.destroy(); this.destroy();
} }
destroy() { destroy(): void {
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); this.destroy$.complete();
} }

44
npm/ng-packs/packages/theme-shared/src/lib/handlers/error.handler.ts

@ -1,25 +1,30 @@
import { HttpErrorReporterService } from '@abp/ng.core';
import { HttpErrorResponse } from '@angular/common/http';
import { inject, Injectable, Injector } from '@angular/core'; import { inject, Injectable, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs'; import { Observable, of, throwError } from 'rxjs';
import { catchError, filter, switchMap } from 'rxjs/operators'; import { catchError, filter, switchMap } from 'rxjs/operators';
import { HttpErrorReporterService } from '@abp/ng.core';
import { CustomHttpErrorHandlerService } from '../models/common'; import { CustomHttpErrorHandlerService } from '../models/common';
import { Confirmation } from '../models/confirmation'; import { Confirmation } from '../models/confirmation';
import { ConfirmationService } from '../services/confirmation.service';
import { CUSTOM_ERROR_HANDLERS, HTTP_ERROR_HANDLER } from '../tokens/http-error.token'; import { CUSTOM_ERROR_HANDLERS, HTTP_ERROR_HANDLER } from '../tokens/http-error.token';
import { HTTP_ERROR_CONFIG } from '../tokens/http-error.token';
import { DEFAULT_ERROR_LOCALIZATIONS, DEFAULT_ERROR_MESSAGES } from '../constants/default-errors'; import { DEFAULT_ERROR_LOCALIZATIONS, DEFAULT_ERROR_MESSAGES } from '../constants/default-errors';
import { ConfirmationService } from '../services/confirmation.service';
import { RouterErrorHandlerService } from '../services/router-error-handler.service'; import { RouterErrorHandlerService } from '../services/router-error-handler.service';
import { HTTP_ERROR_CONFIG } from '../tokens/http-error.token';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ErrorHandler { export class ErrorHandler {
private httpErrorReporter = inject(HttpErrorReporterService); protected readonly httpErrorReporter = inject(HttpErrorReporterService);
private confirmationService = inject(ConfirmationService); protected readonly confirmationService = inject(ConfirmationService);
private routerErrorHandlerService = inject(RouterErrorHandlerService); protected readonly routerErrorHandlerService = inject(RouterErrorHandlerService);
protected httpErrorConfig = inject(HTTP_ERROR_CONFIG); protected readonly httpErrorConfig = inject(HTTP_ERROR_CONFIG);
private customErrorHandlers = inject(CUSTOM_ERROR_HANDLERS); protected readonly customErrorHandlers = inject(CUSTOM_ERROR_HANDLERS);
private defaultHttpErrorHandler = (_, err: HttpErrorResponse) => throwError(() => err);
private httpErrorHandler = protected readonly defaultHttpErrorHandler = (_, err: HttpErrorResponse) => throwError(() => err);
protected readonly httpErrorHandler =
inject(HTTP_ERROR_HANDLER, { optional: true }) || this.defaultHttpErrorHandler; inject(HTTP_ERROR_HANDLER, { optional: true }) || this.defaultHttpErrorHandler;
constructor(protected injector: Injector) { constructor(protected injector: Injector) {
@ -34,17 +39,14 @@ export class ErrorHandler {
protected listenToRestError() { protected listenToRestError() {
this.httpErrorReporter.reporter$ this.httpErrorReporter.reporter$
.pipe(filter(this.filterRestErrors), switchMap(this.executeErrorHandler)) .pipe(filter(this.filterRestErrors), switchMap(this.executeErrorHandler))
.subscribe(err => { .subscribe(err => this.handleError(err));
this.handleError(err);
});
} }
private executeErrorHandler = (error: HttpErrorResponse) => { protected executeErrorHandler = (error: HttpErrorResponse) => {
const errHandler = this.httpErrorHandler(this.injector, error); const errHandler = this.httpErrorHandler(this.injector, error);
const isObservable = errHandler instanceof Observable; const isObservable = errHandler instanceof Observable;
const response = isObservable ? errHandler : of(null);
return response.pipe( return (isObservable ? errHandler : of(null)).pipe(
catchError(err => { catchError(err => {
this.handleError(err); this.handleError(err);
return of(null); return of(null);
@ -59,16 +61,18 @@ export class ErrorHandler {
return (b.priority || 0) - (a.priority || 0); return (b.priority || 0) - (a.priority || 0);
} }
private handleError(err: unknown) { protected handleError(err: unknown) {
if (this.customErrorHandlers && this.customErrorHandlers.length) { if (this.customErrorHandlers && this.customErrorHandlers.length) {
const canHandleService = this.customErrorHandlers const canHandleService = this.customErrorHandlers
.sort(this.sortHttpErrorHandlers) .sort(this.sortHttpErrorHandlers)
.find(service => service.canHandle(err)); .find(service => service.canHandle(err));
if (canHandleService) { if (canHandleService) {
canHandleService.execute(); canHandleService.execute();
return; return;
} }
} }
this.showError().subscribe(); this.showError().subscribe();
} }
@ -90,10 +94,10 @@ export class ErrorHandler {
protected filterRestErrors = ({ status }: HttpErrorResponse): boolean => { protected filterRestErrors = ({ status }: HttpErrorResponse): boolean => {
if (typeof status !== 'number') return false; if (typeof status !== 'number') return false;
if (!this.httpErrorConfig.skipHandledErrorCodes) { if (!this.httpErrorConfig || !this.httpErrorConfig.skipHandledErrorCodes) {
return true; return true;
} }
return this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === status) < 0; return this.httpErrorConfig.skipHandledErrorCodes?.findIndex(code => code === status) < 0;
}; };
} }

6
npm/ng-packs/packages/theme-shared/src/lib/models/common.ts

@ -1,5 +1,5 @@
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { Injector, Type } from '@angular/core'; import { Type } from '@angular/core';
import { Validation } from '@ngx-validate/core'; import { Validation } from '@ngx-validate/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ConfirmationIcons } from '../tokens/confirmation-icons.token'; import { ConfirmationIcons } from '../tokens/confirmation-icons.token';
@ -10,7 +10,7 @@ export interface RootParams {
confirmationIcons?: Partial<ConfirmationIcons>; confirmationIcons?: Partial<ConfirmationIcons>;
} }
export type ErrorScreenErrorCodes = 401 | 403 | 404 | 500; export type ErrorScreenErrorCodes = 0 | 401 | 403 | 404 | 500;
export interface HttpErrorConfig { export interface HttpErrorConfig {
skipHandledErrorCodes?: ErrorScreenErrorCodes[] | number[]; skipHandledErrorCodes?: ErrorScreenErrorCodes[] | number[];
@ -26,5 +26,5 @@ export type LocaleDirection = 'ltr' | 'rtl';
export interface CustomHttpErrorHandlerService { export interface CustomHttpErrorHandlerService {
readonly priority: number; readonly priority: number;
canHandle(error: unknown): boolean; canHandle(error: unknown): boolean;
execute(); execute(): void;
} }

48
npm/ng-packs/packages/theme-shared/src/lib/services/create-error-component.service.ts

@ -8,8 +8,9 @@ import {
Injector, Injector,
RendererFactory2, RendererFactory2,
} from '@angular/core'; } from '@angular/core';
import { Subject } from 'rxjs'; import { DOCUMENT } from '@angular/common';
import { ResolveEnd } from '@angular/router'; import { ResolveEnd } from '@angular/router';
import { Subject } from 'rxjs';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { RouterEvents } from '@abp/ng.core'; import { RouterEvents } from '@abp/ng.core';
import { HTTP_ERROR_CONFIG } from '../tokens/http-error.token'; import { HTTP_ERROR_CONFIG } from '../tokens/http-error.token';
@ -18,31 +19,20 @@ import { ErrorScreenErrorCodes } from '../models/common';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class CreateErrorComponentService { export class CreateErrorComponentService {
protected rendererFactory = inject(RendererFactory2); protected readonly document = inject(DOCUMENT);
protected cfRes = inject(ComponentFactoryResolver); protected readonly rendererFactory = inject(RendererFactory2);
private routerEvents = inject(RouterEvents); protected readonly cfRes = inject(ComponentFactoryResolver);
private injector = inject(Injector); protected readonly routerEvents = inject(RouterEvents);
private httpErrorConfig = inject(HTTP_ERROR_CONFIG); protected readonly injector = inject(Injector);
protected readonly httpErrorConfig = inject(HTTP_ERROR_CONFIG);
componentRef: ComponentRef<HttpErrorWrapperComponent> | null = null; componentRef: ComponentRef<HttpErrorWrapperComponent> | null = null;
private getErrorHostElement() {
return document.body;
}
public canCreateCustomError(status: ErrorScreenErrorCodes) {
return !!(
this.httpErrorConfig?.errorScreen?.component &&
this.httpErrorConfig?.errorScreen?.forWhichErrors &&
this.httpErrorConfig?.errorScreen?.forWhichErrors.indexOf(status) > -1
);
}
constructor() { constructor() {
this.listenToRouterDataResolved(); this.listenToRouterDataResolved();
} }
protected listenToRouterDataResolved() { protected listenToRouterDataResolved(): void {
this.routerEvents this.routerEvents
.getEvents(ResolveEnd) .getEvents(ResolveEnd)
.pipe(filter(() => !!this.componentRef)) .pipe(filter(() => !!this.componentRef))
@ -52,11 +42,25 @@ export class CreateErrorComponentService {
}); });
} }
private isCloseIconHidden() { protected getErrorHostElement(): HTMLElement {
return !!this.httpErrorConfig.errorScreen?.hideCloseIcon; return this.document.body;
}
protected isCloseIconHidden(): boolean {
return !!this.httpErrorConfig?.errorScreen?.hideCloseIcon;
}
canCreateCustomError(status: ErrorScreenErrorCodes) {
const { component, forWhichErrors } = this.httpErrorConfig?.errorScreen || {};
if (!component || !forWhichErrors) {
return false;
}
return forWhichErrors.indexOf(status) > -1;
} }
execute(instance: Partial<HttpErrorWrapperComponent>) { execute(instance: Partial<HttpErrorWrapperComponent>): void {
const renderer = this.rendererFactory.createRenderer(null, null); const renderer = this.rendererFactory.createRenderer(null, null);
const hostElement = this.getErrorHostElement(); const hostElement = this.getErrorHostElement();
const host = renderer.selectRootElement(hostElement, true); const host = renderer.selectRootElement(hostElement, true);

29
npm/ng-packs/packages/theme-shared/src/lib/services/router-error-handler.service.ts

@ -1,42 +1,43 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { NavigationError } from '@angular/router';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { RouterEvents } from '@abp/ng.core'; import { RouterEvents } from '@abp/ng.core';
import { NavigationError } from '@angular/router';
import { HTTP_ERROR_CONFIG } from '../tokens/'; import { HTTP_ERROR_CONFIG } from '../tokens/';
import { CreateErrorComponentService } from '../services'; import { CreateErrorComponentService } from '../services';
import { DEFAULT_ERROR_LOCALIZATIONS, DEFAULT_ERROR_MESSAGES } from '../constants/default-errors'; import { DEFAULT_ERROR_LOCALIZATIONS, DEFAULT_ERROR_MESSAGES } from '../constants/default-errors';
import { ErrorScreenErrorCodes } from '../models';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class RouterErrorHandlerService { export class RouterErrorHandlerService {
private readonly routerEvents = inject(RouterEvents); protected readonly routerEvents = inject(RouterEvents);
private httpErrorConfig = inject(HTTP_ERROR_CONFIG); protected readonly httpErrorConfig = inject(HTTP_ERROR_CONFIG);
private createErrorComponentService = inject(CreateErrorComponentService); protected readonly createErrorComponentService = inject(CreateErrorComponentService);
listen() {
this.routerEvents
.getNavigationEvents('Error')
.pipe(filter(this.filterRouteErrors))
.subscribe(() => this.show404Page());
}
protected filterRouteErrors = (navigationError: NavigationError): boolean => { protected filterRouteErrors = (navigationError: NavigationError): boolean => {
if (!this.httpErrorConfig.skipHandledErrorCodes) { if (!this.httpErrorConfig?.skipHandledErrorCodes) {
return true; return true;
} }
return ( return (
navigationError.error?.message?.indexOf('Cannot match') > -1 && navigationError.error?.message?.indexOf('Cannot match') > -1 &&
this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === 404) < 0 this.httpErrorConfig.skipHandledErrorCodes.findIndex(code => code === 404) < 0
); );
}; };
listen() {
this.routerEvents
.getNavigationEvents('Error')
.pipe(filter(this.filterRouteErrors))
.subscribe(() => this.show404Page());
}
show404Page() { show404Page() {
const instance = { const instance = {
title: { title: {
key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title, key: DEFAULT_ERROR_LOCALIZATIONS.defaultError404.title,
defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title, defaultValue: DEFAULT_ERROR_MESSAGES.defaultError404.title,
}, },
status: 404, status: <ErrorScreenErrorCodes>404,
}; };
this.createErrorComponentService.execute(instance); this.createErrorComponentService.execute(instance);

88
npm/ng-packs/packages/theme-shared/src/lib/services/status-code-error-handler.service.ts

@ -12,19 +12,57 @@ import { CreateErrorComponentService } from './create-error-component.service';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class StatusCodeErrorHandlerService implements CustomHttpErrorHandlerService { export class StatusCodeErrorHandlerService implements CustomHttpErrorHandlerService {
private readonly confirmationService = inject(ConfirmationService); protected readonly confirmationService = inject(ConfirmationService);
private readonly createErrorComponentService = inject(CreateErrorComponentService); protected readonly createErrorComponentService = inject(CreateErrorComponentService);
private readonly authService = inject(AuthService); protected readonly authService = inject(AuthService);
protected readonly handledStatusCodes = [401, 403, 404, 500] as const;
protected status: typeof this.handledStatusCodes[number];
readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.normal; readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.normal;
private status: typeof this.handledStatusCodes[number];
private readonly handledStatusCodes = [401, 403, 404, 500] as const; protected navigateToLogin(): void {
this.authService.navigateToLogin();
}
protected showConfirmation(
message: LocalizationParam,
title: LocalizationParam,
): Observable<Confirmation.Status> {
return this.confirmationService.error(message, title, {
hideCancelBtn: true,
yesText: 'AbpAccount::Close',
});
}
protected showPage(): void {
const key = `defaultError${this.status}`;
const shouldRemoveDetail = [401, 404].indexOf(this.status) > -1;
const instance = {
title: {
key: DEFAULT_ERROR_LOCALIZATIONS[key]?.title,
defaultValue: DEFAULT_ERROR_MESSAGES[key]?.title,
},
details: {
key: DEFAULT_ERROR_LOCALIZATIONS[key]?.details,
defaultValue: DEFAULT_ERROR_MESSAGES[key]?.details,
},
status: this.status,
};
if (shouldRemoveDetail) {
delete instance.details;
}
this.createErrorComponentService.execute(instance);
}
canHandle({ status }): boolean { canHandle({ status }): boolean {
this.status = status; this.status = status || 0;
return this.handledStatusCodes.indexOf(status) > -1; return this.handledStatusCodes.indexOf(status) > -1;
} }
execute() { execute(): void {
const key = `defaultError${this.status}`; const key = `defaultError${this.status}`;
const title = { const title = {
key: DEFAULT_ERROR_LOCALIZATIONS[key]?.title, key: DEFAULT_ERROR_LOCALIZATIONS[key]?.title,
@ -36,6 +74,7 @@ export class StatusCodeErrorHandlerService implements CustomHttpErrorHandlerServ
}; };
const canCreateCustomError = this.createErrorComponentService.canCreateCustomError(this.status); const canCreateCustomError = this.createErrorComponentService.canCreateCustomError(this.status);
switch (this.status) { switch (this.status) {
case 401: case 401:
case 404: case 404:
@ -56,39 +95,4 @@ export class StatusCodeErrorHandlerService implements CustomHttpErrorHandlerServ
break; break;
} }
} }
private navigateToLogin() {
this.authService.navigateToLogin();
}
protected showConfirmation(
message: LocalizationParam,
title: LocalizationParam,
): Observable<Confirmation.Status> {
return this.confirmationService.error(message, title, {
hideCancelBtn: true,
yesText: 'AbpAccount::Close',
});
}
protected showPage() {
const key = `defaultError${this.status}`;
const instance = {
title: {
key: DEFAULT_ERROR_LOCALIZATIONS[key]?.title,
defaultValue: DEFAULT_ERROR_MESSAGES[key]?.title,
},
details: {
key: DEFAULT_ERROR_LOCALIZATIONS[key]?.details,
defaultValue: DEFAULT_ERROR_MESSAGES[key]?.details,
},
status: this.status,
};
const shouldRemoveDetail = [401, 404].indexOf(this.status) > -1;
if (shouldRemoveDetail) {
delete instance.details;
}
this.createErrorComponentService.execute(instance);
}
} }

Loading…
Cancel
Save