Base solution for your next web application
Open Closed

Override the error popup windows in Angular #5826


User avatar
0
tomrapdebruyne created

Hello guys, I am trying to override default behavior when error occur on API side and that is: throw error in dialog (modal view - please check first image). I would like to replace it with toast notification (second image). Is there any way to do it ?

Release: .Net Core + Angular ASPNETZero: v5.5

Thanks in advance, Peter


6 Answer(s)
  • User Avatar
    1
    ismcagdas created
    Support Team

    If you want to do this for all error messages, you can create a similar javascript file for the https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.Web.Resources/Abp/Framework/scripts/libs/abp.sweet-alert.js and iclude it into angular.json instead of abp.sweet-alert.js.

    If you want to do this only for server-side error messages, you need to write your own http interceptor similar to https://github.com/aspnetboilerplate/abp-ng2-module/blob/master/src/abpHttpInterceptor.ts and use it instead of AbpHttpInterceptor in your app.

  • User Avatar
    0
    tomrapdebruyne created

    Hello sir, thank you for response. I started implementing second solution, that is to create my own HTTP interceptor, and issue ocurred:

    Response errors are thrown twice for some reason, please check provided images and code, maybe you will have more information for me...

    On first image we can see that there is one error (type 500):


    On second image you can see that there are two toast erros shown on UI:


    This is my customHttpInterceptor (used instead of abpHttpInterceptor)

    import { Injectable } from '@angular/core';
    import { Observable, Subject, of } from 'rxjs';
    
    import { HttpClient, HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
    import { MessageService } from 'abp-ng2-module/dist/src/message/message.service';
    import { LogService } from 'abp-ng2-module/dist/src/log/log.service';
    import { TokenService } from 'abp-ng2-module/dist/src/auth/token.service';
    import { UtilsService } from 'abp-ng2-module/dist/src/utils/utils.service';
    import { NotifyService } from 'abp-ng2-module/dist/src/notify/notify.service';
    import { SignalRService } from '@app/shared/common/signalr/signalRService';
    
    
    export interface IValidationErrorInfo {
    
        message: string;
    
        members: string[];
    
    }
    
    export interface IErrorInfo {
    
        code: number;
    
        message: string;
    
        details: string;
    
        validationErrors: IValidationErrorInfo[];
    
    }
    
    export interface IAjaxResponse {
    
        success: boolean;
    
        result?: any;
    
        targetUrl?: string;
    
        error?: IErrorInfo;
    
        unAuthorizedRequest: boolean;
    
        __abp: boolean;
    
    }
    
    @Injectable()
    // tslint:disable-next-line:class-name
    export class customHttpConfiguration {
    
        constructor(
            private _messageService: MessageService,
            private _logService: LogService,
            private _notify: NotifyService) {
    
        }
    
        defaultError = <IErrorInfo>{
            message: 'An error has occurred!',
            details: 'Error details were not sent by server.'
        };
    
        defaultError401 = <IErrorInfo>{
            message: 'You are not authenticated!',
            details: 'You should be authenticated (sign in) in order to perform this operation.'
        };
    
        defaultError403 = <IErrorInfo>{
            message: 'You are not authorized!',
            details: 'You are not allowed to perform this operation.'
        };
    
        defaultError404 = <IErrorInfo>{
            message: 'Resource not found!',
            details: 'The resource requested could not be found on the server.'
        };
    
        logError(error: IErrorInfo): void {
            this._logService.error(error);
        }
    
        showError(error: IErrorInfo): any {
            if (error.details) {
                return this._messageService.error(error.details, error.message || this.defaultError.message);
            } else {
                return this._messageService.error(error.message || this.defaultError.message);
            }
        }
    
        toastError(error: IErrorInfo): void {
            if (error.details) {
                return this._notify.error(error.details, error.message || this.defaultError.message);
            } else {
                return this._notify.error(error.message || this.defaultError.message);
            }
        }
    
        handleTargetUrl(targetUrl: string): void {
            if (!targetUrl) {
                location.href = '/';
            } else {
                location.href = targetUrl;
            }
        }
    
        handleUnAuthorizedRequest(messagePromise: any, targetUrl?: string) {
            const self = this;
    
            if (messagePromise) {
                messagePromise.done(() => {
                    this.handleTargetUrl(targetUrl || '/');
                });
            } else {
                self.handleTargetUrl(targetUrl || '/');
            }
        }
    
        handleNonAbpErrorResponse(response: HttpResponse<any>) {
            const self = this;
            switch (response.status) {
                case 401:
                    self.handleUnAuthorizedRequest(
                        self.showError(self.defaultError401),
                        '/'
                    );
                    break;
                case 403:
                    self.showError(self.defaultError403);
                    break;
                case 404:
                    self.showError(self.defaultError404);
                    break;
                default:
                    self.showError(self.defaultError);
                    break;
            }
        }
    
        handleAbpResponse(response: HttpResponse<any>, ajaxResponse: IAjaxResponse): HttpResponse<any> {
            let newResponse: HttpResponse<any>;
    
            if (ajaxResponse.success) {
    
                newResponse = response.clone({
                    body: ajaxResponse.result
                });
    
                if (ajaxResponse.targetUrl) {
                    this.handleTargetUrl(ajaxResponse.targetUrl);
                }
            } else {
    
                newResponse = response.clone({
                    body: ajaxResponse.result
                });
    
                if (!ajaxResponse.error) {
                    ajaxResponse.error = this.defaultError;
                }
    
                if (response.status === 500) {
                    // this.logError(ajaxResponse.error);
                    this.toastError(ajaxResponse.error);
                } else {
                    this.logError(ajaxResponse.error);
                    this.showError(ajaxResponse.error);
                }
                if (response.status === 401) {
                    this.handleUnAuthorizedRequest(null, ajaxResponse.targetUrl);
                }
            }
    
            return newResponse;
        }
    
        getAbpAjaxResponseOrNull(response: HttpResponse<any>): IAjaxResponse | null {
            if (!response || !response.headers) {
                return null;
            }
    
            let contentType = response.headers.get('Content-Type');
            if (!contentType) {
                this._logService.warn('Content-Type is not sent!');
                return null;
            }
    
            if (contentType.indexOf('application/json') < 0) {
                this._logService.warn('Content-Type is not application/json: ' + contentType);
                return null;
            }
    
            let responseObj = JSON.parse(JSON.stringify(response.body));
            if (!responseObj.__abp) {
                return null;
            }
    
            return responseObj as IAjaxResponse;
        }
    
        handleResponse(response: HttpResponse<any>): HttpResponse<any> {
            let ajaxResponse = this.getAbpAjaxResponseOrNull(response);
            if (ajaxResponse == null) {
                return response;
            }
            return this.handleAbpResponse(response, ajaxResponse);
        }
    
        blobToText(blob: any): Observable<string> {
            return new Observable<string>((observer: any) => {
                if (!blob) {
                    observer.next('');
                    observer.complete();
                } else {
                    let reader = new FileReader();
                    reader.onload = function () {
                        observer.next(this.result);
                        observer.complete();
                    };
                    reader.readAsText(blob);
                }
            });
        }
    }
    
    @Injectable()
    // tslint:disable-next-line:class-name
    export class customHttpInterceptor implements HttpInterceptor {
    
        protected configuration: customHttpConfiguration;
        private _tokenService: TokenService = new TokenService();
        private _utilsService: UtilsService = new UtilsService();
        private _logService: LogService = new LogService();
    
        constructor(configuration: customHttpConfiguration) {
            this.configuration = configuration;
        }
    
        intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            let interceptObservable = new Subject<HttpEvent<any>>();
            let modifiedRequest = this.normalizeRequestHeaders(request);
    
            next.handle(modifiedRequest)
                .subscribe((event: HttpEvent<any>) => {
                    this.handleSuccessResponse(event, interceptObservable);
                }, (error: any) => {
                    return this.handleErrorResponse(error, interceptObservable);
                });
            return interceptObservable;
    
        }
    
        protected normalizeRequestHeaders(request: HttpRequest<any>): HttpRequest<any> {
            let modifiedHeaders = new HttpHeaders();
            modifiedHeaders = request.headers.set('Pragma', 'no-cache')
                .set('Cache-Control', 'no-cache')
                .set('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT');
    
            modifiedHeaders = this.addXRequestedWithHeader(modifiedHeaders);
            modifiedHeaders = this.addAuthorizationHeaders(modifiedHeaders);
            modifiedHeaders = this.addAspNetCoreCultureHeader(modifiedHeaders);
            modifiedHeaders = this.addAcceptLanguageHeader(modifiedHeaders);
            modifiedHeaders = this.addTenantIdHeader(modifiedHeaders);
    
            return request.clone({
                headers: modifiedHeaders
            });
        }
    
        protected addXRequestedWithHeader(headers: HttpHeaders): HttpHeaders {
            if (headers) {
                headers = headers.set('X-Requested-With', 'XMLHttpRequest');
            }
    
            return headers;
        }
    
        protected addAspNetCoreCultureHeader(headers: HttpHeaders): HttpHeaders {
            let cookieLangValue = this._utilsService.getCookieValue('Abp.Localization.CultureName');
            if (cookieLangValue && headers && !headers.has('.AspNetCore.Culture')) {
                headers = headers.set('.AspNetCore.Culture', cookieLangValue);
            }
    
            return headers;
        }
    
        protected addAcceptLanguageHeader(headers: HttpHeaders): HttpHeaders {
            let cookieLangValue = this._utilsService.getCookieValue('Abp.Localization.CultureName');
            if (cookieLangValue && headers && !headers.has('Accept-Language')) {
                headers = headers.set('Accept-Language', cookieLangValue);
            }
    
            return headers;
        }
    
        protected addTenantIdHeader(headers: HttpHeaders): HttpHeaders {
            let cookieTenantIdValue = this._utilsService.getCookieValue('Abp.TenantId');
            if (cookieTenantIdValue && headers && !headers.has('Abp.TenantId')) {
                headers = headers.set('Abp.TenantId', cookieTenantIdValue);
            }
    
            return headers;
        }
    
        protected addAuthorizationHeaders(headers: HttpHeaders): HttpHeaders {
            let authorizationHeaders = headers ? headers.getAll('Authorization') : null;
            if (!authorizationHeaders) {
                authorizationHeaders = [];
            }
    
            if (!this.itemExists(authorizationHeaders, (item: string) => item.indexOf('Bearer ') === 0)) {
                let token = this._tokenService.getToken();
                if (headers && token) {
                    headers = headers.set('Authorization', 'Bearer ' + token);
                }
            }
    
            return headers;
        }
    
        protected handleSuccessResponse(event: HttpEvent<any>, interceptObservable: Subject<HttpEvent<any>>): void {
            let self = this;
    
            if (event instanceof HttpResponse) {
                if (event.body instanceof Blob && event.body.type && event.body.type.indexOf('application/json') >= 0) {
                    let clonedResponse = event.clone();
    
                    self.configuration.blobToText(event.body).subscribe(json => {
                        const responseBody = json === 'null' ? {} : JSON.parse(json);
    
                        let modifiedResponse = self.configuration.handleResponse(event.clone({
                            body: responseBody
                        }));
    
                        interceptObservable.next(modifiedResponse.clone({
                            body: new Blob([JSON.stringify(modifiedResponse.body)], { type: 'application/json' })
                        }));
    
                        interceptObservable.complete();
                    });
                } else {
                    interceptObservable.next(event);
                    interceptObservable.complete();
                }
            }
        }
    
        protected handleErrorResponse(error: any, interceptObservable: Subject<HttpEvent<any>>): Observable<any> {
            let errorObservable = new Subject<any>();
    
            if (!(error.error instanceof Blob)) {
                interceptObservable.error(error);
                interceptObservable.complete();
                return of({});
            }
    
            this.configuration.blobToText(error.error).subscribe(json => {
                const errorBody = (json === '' || json === 'null') ? {} : JSON.parse(json);
                const errorResponse = new HttpResponse({
                    headers: error.headers,
                    status: error.status,
                    body: errorBody
                });
    
                let ajaxResponse = this.configuration.getAbpAjaxResponseOrNull(errorResponse);
    
                if (ajaxResponse != null) {
                    this.configuration.handleAbpResponse(errorResponse, ajaxResponse);
                } else {
                    this.configuration.handleNonAbpErrorResponse(errorResponse);
                }
    
                errorObservable.complete();
    
                interceptObservable.error(error);
                interceptObservable.complete();
            });
    
            return errorObservable;
        }
    
        private itemExists<T>(items: T[], predicate: (item: T) => boolean): boolean {
            for (let i = 0; i < items.length; i++) {
                if (predicate(items[i])) {
                    return true;
                }
            }
    
            return false;
        }
    }
    
    

    This is my root module (providers part of it). Here I provide customHttpInterceptor and you can see that I added customHttpConfiguration as well

    providers: [
         **   { provide: HTTP_INTERCEPTORS, useClass: customHttpInterceptor, multi: true },**
            { provide: API_BASE_URL, useFactory: getRemoteServiceBaseUrl },
            {
                provide: APP_INITIALIZER,
                useFactory: appInitializerFactory,
                deps: [Injector, PlatformLocation],
                multi: true
            },
            {
                provide: LOCALE_ID,
                useFactory: getCurrentLanguage
            },
            AppInsightsService,
            AppTranslationService,
           ** customHttpConfiguration**
        ],
    

    Am I doing something wrong ?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @tomrapdebruyne

    Have you removed the usages of AbpHttpInterceptor ? You should only use your own version in this case.

  • User Avatar
    0
    tomrapdebruyne created

    Hello @ismcagdas, thanks for fast response...

    Yes, it is fully removed from project (from root module imports and providers as well from service proxy module import and providers).

    Still same issue - double erros for one request.

    I did some testing and it seems that even with AbpHttpInterceptor logic I have same issue, it is just that sweet-alert is used (dialog error) and it handles it somehow correctly, but logging of error is done twice as well.

    Do you know any possible cause or solution for this ?

  • User Avatar
    0
    ismcagdas created
    Support Team

    @tomrapdebruyne

    Are you able to expand the javascript errors and see if the both error logs have the same origin (source code) ?

  • User Avatar
    0
    tomrapdebruyne created

    @ismcagdas

    I've managed to solve this problem, for some reason I had to give interceptor name: abpHttpInterceptor, not customHttpInterceptor,using that name (abpHttpInterceptor) solved my problem of having double error popups.

    Thank you for your help.