code finder
code finder

Reputation: 11

Angular 2 - error handling

Angular 2 - I have many pages in my application and where in each page I am calling the backend services. Here my query was how to create a common error-popup component where it will take error message and show the popup when ever it required my application. Can you please help to find the solution on this. Thank you.

Upvotes: 1

Views: 252

Answers (2)

Isaac Obella
Isaac Obella

Reputation: 2643

For user notification on errors, i use Angular Material. On running an http query. I handle errors from http observables using the error=> arrow function in the Observable result. Forexample this is an auth login query to my restful API.

...
constructor(private _dialogService: TdDialogService,
              private _viewContainerRef: ViewContainerRef,
              private authenticationService: AuthenticationService){}
       login() {
    this.authenticationService.login(this.formLogin.value.email, this.formLogin.value.password)
      .subscribe(
        data => {
          // Handle any 200 result
        },
        error => {
          // Handle any http errors, 401 UnAuthorized or 500 or any other
          this.openAlert('Invalid username or password'); // You can pass in your error message
        });
  }

  openAlert(message: string) {
    this._dialogService.openAlert({
      message,
      disableClose: true,
      viewContainerRef: this._viewContainerRef,
      title: 'Error',
      closeButton: 'Ok'
    });
  }
}

Result

enter image description here

Upvotes: 0

Joe Belladonna
Joe Belladonna

Reputation: 1339

This is how I did it in Angular 5, you can do it similar in Angular2, since it is not that different. I hope this will help you. In my app.component.html, I added this:

<div class="mesaages">
  <div *ngIf="errorMessage" class="alert alert-danger alert-dismissible fade show" role="alert">
      <button type="button" class="close" (click)="errorMessage=''">
                          <span aria-hidden="true">&times;</span> 
                      </button>
      <i class="fa fa-exclamation-circle fa-1x" aria-hidden="true"></i> {{ errorMessage}} 
  </div>

  <div *ngIf="busyinfoMessage" class="alert alert-success alert-dismissible fade show" role="alert">
      <button type="button" class="close" (click)="busyinfoMessage=''">
                          <span aria-hidden="true">&times;</span>
                      </button>
      <i class="fa fa-spinner fa-pulse fa-1x fa-fw" aria-hidden="true" placement="right"></i> {{ busyinfoMessage}}
  </div>

  <div *ngIf="infoMessage" class="alert alert-info alert-dismissible fade show" (mouseover)="stopTimeout('w')" (mouseout)="_actionService.infoMessage(infoMessage,3)" role="alert">
      <button type="button" class="close" (click)="infoMessage=''">
                          <span aria-hidden="true">&times;</span>
                      </button>
      <i class="fa fa-info-circle fa-1x" aria-hidden="true" placement="right"></i> {{ infoMessage}}
  </div>

  <div *ngIf="warningMessage" class="alert alert-warning alert-dismissible fade show" (mouseover)="stopTimeout('w')" (mouseout)="_actionService.warningMessage(warningMessage,3)" role="alert">
    <button type="button" class="close" (click)="warningMessage=''">
                        <span aria-hidden="true">&times;</span>
                    </button>
    <i class="fa fa-bell fa-1x" aria-hidden="true"></i> {{ warningMessage}}
</div>

in my CSS file I added this:

.messages {
  float: left;
  top: 10px;
  right: 10px;
  width: 100;
  height: 20;
  padding: 6px;
  position: fixed;
  border-radius: 8px;
  z-index: 9999;
}

This css code will place this toast like message in right upper corner of page. Then I cretated action.service.ts which is used for showing this messages and to toggle my loader-spinner:

import { Injectable, EventEmitter } from '@angular/core';

@Injectable()
export class ActionService {
    public autoloader = true;
    public _toggleLoader: EventEmitter<any>;
    public _errorMessage: EventEmitter<any>;
    public _busyinfoMessage: EventEmitter<any>;
    public _warningMessage: EventEmitter<any>;
    public _infoMessage: EventEmitter<any>;
    public timeoutInfo: any;
    public timeoutWarning: any;

    constructor() {
        this._toggleLoader = new EventEmitter<any>();
        this._errorMessage = new EventEmitter<any>();
        this._busyinfoMessage = new EventEmitter<any>();
        this._warningMessage = new EventEmitter<any>();
        this._infoMessage = new EventEmitter<any>();
    }

    toggleLoader(toggle: boolean) {
        this._toggleLoader.emit(toggle);
    }

    errorMessage(errorMessage: string) {
        this._errorMessage.emit(errorMessage);
    }

    busyinfoMessage(busyinfoMessage: string) {
        this._busyinfoMessage.emit(busyinfoMessage);
    }

    warningMessage(warningMessage: string, timeout: number) {
        if (timeout === 0) {
            this._warningMessage.emit(warningMessage);
            return;
        }
        this.timeoutWarning = setTimeout(() => {
            this._warningMessage.emit('');
        }, 1000 * timeout);
        this._warningMessage.emit(warningMessage);
    }

    infoMessage(infoMessage: string, timeout: number) {
        if (timeout === 0) {
            this._infoMessage.emit(infoMessage);
            return;
        }
        this.timeoutInfo = setTimeout(() => {
            this._infoMessage.emit('');
        }, 1000 * timeout);
        this._infoMessage.emit(infoMessage);
    }

    stopTimeout(tip: string) {
        if (tip === 'w') {
            clearTimeout(this.timeoutWarning);
        } else {
            clearTimeout(this.timeoutInfo);
        }
    }    
    }
}

Im my app.component.ts file I added this lines of code:

import { Component, OnInit, OnDestroy } from '@angular/core';

import { Subject } from 'rxjs/Subject';

import { ActionService } from './action.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit, OnDestroy {
  private ngUnsubscribe: Subject<any> = new Subject();
  public loader = false;
  public errorMessage = '';
  public warningMessage = '';
  public busyinfoMessage = '';
  public infoMessage = '';

  constructor(
    private _router: Router,
    private _actionService: ActionService
  ) {
  }

  ngOnInit() {
    this._actionService._toggleLoader
      .takeUntil(this.ngUnsubscribe)
      .subscribe(res => {
        setTimeout(() => this.loader = res, 0);
      });

    this._actionService._errorMessage
      .takeUntil(this.ngUnsubscribe)
      .subscribe(res => {
        this.errorMessage = res;
      });

    this._actionService._warningMessage
      .takeUntil(this.ngUnsubscribe)
      .subscribe(res => {
        this.warningMessage = res;
      });

    this._actionService._busyinfoMessage
      .takeUntil(this.ngUnsubscribe)
      .subscribe(res => {
        this.busyinfoMessage = res;
      });

    this._actionService._infoMessage
      .takeUntil(this.ngUnsubscribe)
      .subscribe(res => {
        this.infoMessage = res;
      });

  }

  stopTimeout(tip: string) {
    this._actionService.stopTimeout(tip);
  }

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }
}

Since I'm using Angular 5 I created this error interceptor for showing error messages after every call:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import { ActionService } from './action.service';
import { Router } from '@angular/router';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {

    constructor(
        private _actionService: ActionService,
        private _router: Router) {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(request)
            .catch((err: HttpErrorResponse, caught) => {
                if (err.status === 0) {
                    this._actionService.errorMessage('Check you connection to interent!');
                    return Observable.empty<HttpEvent<any>>();
                }
                if (err.status === 500) {
                    this._actionService.errorMessage('Server error : ' + err.error.ExceptionMessage);
                    return Observable.empty<HttpEvent<any>>();
                }
                if (err.status === 401 || err.status === 403) {
                    this._router.navigateByUrl('prijava');
                    return Observable.empty<HttpEvent<any>>();
                }
                this._actionService.errorMessage('Unknown error. Error message: ' + (err.error.ExceptionMessage || err.error.Message));
                return Observable.empty<HttpEvent<any>>();
            })
    }
}

export const ErrorInterceptorProvider = {
    provide: HTTP_INTERCEPTORS,
    useClass: ErrorInterceptor,
    multi: true
};

Since you are using Angular 2, my suggestion to you would be to create HTTP.Service and to implemet calls for errors in that service. Something like this:

import { Injectable, ReflectiveInjector } from '@angular/core';
import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '@angular/http';
import { LocatorService } from './locator.service';
import { Observable } from 'rxjs/Observable';
import { Router } from '@angular/router';
import { ActionService } from './action.service';

import { Url } from './global';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class HttpService extends Http {
    private currentRequests: number;
    private _router: Router;
    private _actionService: ActionService;

    constructor(backend: XHRBackend, options: RequestOptions, router: Router) {
        let token = localStorage.getItem('token'); // your custom token getter function here
        options.url = Url + (options.url == null ? '' : options.url);
        options.headers.set('Authorization', `Bearer ${token}`);
        super(backend, options);
        this._actionService = LocatorService.injector.get(ActionService);
        this._router = router;
        this.currentRequests = 0;
    }

    request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
        let token = localStorage.getItem('token');
        if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
            if (!options) {
                // let's make option object
                options = { headers: new Headers() };
            }
            options.headers.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
            options.url = Url + url;
        } else {
            // we have to add the token to the url object
            if (!options) {
                // let's make option object
                options = { headers: new Headers() };
            }
            options.headers.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
            url.url = Url + url.url;
        }        
        if (this._actionService.autoloader) { this._actionService.toggleLoader(true); }
        return super.request(url, options)
            .catch(
            this.catchError(this)
            )
            .finally(() => {
                setTimeout(() => {                    
                    if (this._actionService.autoloader) { this._actionService.toggleLoader(false); }
                }, 100);
            }
            );
    }   

    private catchError(self: HttpService) {
        // we have to pass HttpService's own instance here as `self`
        return (res: Response) => {
            console.log(res);
            switch (res.status) {
                case 0:
                    this._actionService.errorMessage('Check your internet connection!');
                    return Observable.throw(null);
                case 500:
                    this._actionService.errorMessage('Server error : ' + res.json()['ExceptionMessage']);
                    return Observable.throw(null);
                case 401:
                case 403:
                    this._router.navigateByUrl('prijava');
                    return Observable.throw(null);
                default:
                    this._actionService.errorMessage('Unknown error. Error message: ' + (res.json()['ExceptionMessage'] || res.json()['Message']));
                    return Observable.throw(null);
            }
        };
    }
}

I hope that is all. If you need any clarification please ask. I hope this will help you.

Upvotes: 1

Related Questions