28

I, am trying to handle the http error using the below class in angular 6. I got a 401 unAuthorized status from server. But however I, don't see the console error message.

HttpErrorsHandler.ts file

import { ErrorHandler, Injectable} from '@angular/core';
    @Injectable()
    export class HttpErrorsHandler implements ErrorHandler {
      handleError(error: Error) {
         // Do whatever you like with the error (send it to the server?)
         // And log it to the console
         console.error('It happens: ', error);
      }
    }

http error

app.module.ts file

providers: [{provide: ErrorHandler, useClass: HttpErrorsHandler}],

HttpCallFile

import { Injectable , Component} from '@angular/core';
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Observable } from 'rxjs';
import {AuthServiceJwt} from '../Common/sevice.auth.component';

@Injectable()
export class GenericHttpClientService {
    private readonly baseUrl : string = "**********";


    constructor(private httpClientModule: HttpClient , private authServiceJwt : AuthServiceJwt)  {
    }

    public GenericHttpPost<T>(_postViewModel: T , destinationUrl : string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json; charset=utf-8')
            .set('Authorization',`Bearer ${this.authServiceJwt.getToken}`);
        return this.httpClientModule.post<T>(this.baseUrl + destinationUrl, _postViewModel, { headers });
    }

    // This method is to post Data and Get Response Data in two different type
    public GenericHttpPostAndResponse<T,TE>(postViewModel: TE, destinationUrl: string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json; charset=utf-8')
            .set('Authorization',`Bearer ${this.authServiceJwt.getToken}`);
        return this.httpClientModule.post<T>(this.baseUrl + destinationUrl, postViewModel, { headers });
    }

    // This method is to post Data and Get Response Data in two different type without JWT Token
    public GenericHttpPostWithOutToken<T,TE>(postViewModel: TE, destinationUrl: string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json; charset=utf-8');
        return this.httpClientModule.post<T>(this.baseUrl + destinationUrl, postViewModel, { headers });
    }

    public GenericHttpGet<T>(destinationUrl: string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json')
            .set('Authorization',`Bearer ${this.authServiceJwt.getToken}`);
        return this.httpClientModule.get<T>(this.baseUrl + destinationUrl, { headers });
    }

    public GenericHttpDelete<T>(destinationUrl: string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json')
            .set('Authorization',`Bearer ${this.authServiceJwt.getToken}`);
        return this.httpClientModule.delete<T>(this.baseUrl + destinationUrl, { headers });
    }
}

admin.user.component.ts file

private getUsersHttpCall(): void {
    this.spinnerProgress = true;
    this.genericHttpService.GenericHttpGet<GenericResponseObject<UserViewModel[]>>(this.getAdminUserUrl).subscribe(data => {
      if (data.isSuccess) {
        this.genericResponseObject.data = data.data;
        this.dataSource = this.genericResponseObject.data
        this.spinnerProgress = false;
      }

    }, error => {
      console.log(error);
      this.spinnerProgress = false;
    });
  }
San Jaisy
  • 15,327
  • 34
  • 171
  • 290

3 Answers3

45

For XHR request you should use an Interceptor

This is the one I use to add JWT to headers and to handle some response errors:

import {Injectable} from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor, HttpErrorResponse
} from '@angular/common/http';
import {AuthService} from '../service/auth.service';
import {Observable, of} from 'rxjs';
import {Router} from "@angular/router";
import {catchError} from "rxjs/internal/operators";

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  constructor(public auth: AuthService, private router: Router) {
  }


  /**
   * intercept all XHR request
   * @param request
   * @param next
   * @returns {Observable<A>}
   */
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    if (localStorage.getItem('jwtToken')) {
      request = request.clone({
        setHeaders: {
          Authorization: 'Bearer ' + localStorage.getItem('jwtToken')
        }
      });
    }

    /**
     * continues request execution
     */
    return next.handle(request).pipe(catchError((error, caught) => {
        //intercept the respons error and displace it to the console
        console.log(error);
        this.handleAuthError(error);
        return of(error);
      }) as any);
  }


  /**
   * manage errors
   * @param err
   * @returns {any}
   */
  private handleAuthError(err: HttpErrorResponse): Observable<any> {
    //handle your auth error or rethrow
    if (err.status === 401) {
      //navigate /delete cookies or whatever
      console.log('handled error ' + err.status);
      this.router.navigate([`/login`]);
      // if you've caught / handled the error, you don't want to rethrow it unless you also want downstream consumers to have to handle it as well.
      return of(err.message);
    }
    throw err;
  }
}

Don't forget to register you interceptor into app.module.ts like so:

import { TokenInterceptor } from './auth/token.interceptor';

@NgModule({
  declarations: [],
  imports: [],
  exports: [],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true,
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
MAESTRO_DE
  • 433
  • 2
  • 17
firegloves
  • 5,581
  • 2
  • 29
  • 50
  • you must import of from rxjs. if you are using a ide it should suggest you – firegloves Jun 21 '18 at 15:34
  • This will eat HTTP errors that are NOT 401|403 errors. You have to `throw` the error if it doesn't match the error code, so that other `.pipe`'s can handle it. Also, handling 403 errors shouldn't be done; just because the user tries something they're forbidden to do, doesn't mean they need to log in again. – amphetamachine Jul 31 '18 at 16:38
  • @amphetamachine why are you saying so? – firegloves Jul 31 '18 at 16:42
  • @amphetamachine it is only an example on how to catch http errors. yeah about 403 you have reason, I've updated my answer. I can't understand your first phrase – firegloves Jul 31 '18 at 17:01
  • 1
    @firegloves I have services that add their own `.pipe` to handle errors for their API calls. Saying `return of(error);` implicitly makes it fail to pass the non-401 errors to the service's `.pipe`. Using your example, I made my code work for both types of errors by saying `if (error.status === 401) { this.handleAuthError(error); return of(error); } throw error;` – amphetamachine Jul 31 '18 at 17:30
  • 1
    I got this to work. handleAuthError() should end with "throw err", not "throw error". – TomKraljevic Dec 28 '18 at 02:00
10

From @firegloves's answer, in order to have the .pipe handlers in individual services actually be able to catchError their own HTTP failure codes, you will need to structure the code such that:

  1. If the Interceptor detects an HTTP 401 error code, handle it and return of(error) which can potentially be handled normally in any subsequent .pipe's.
  2. If the HTTP error code is one the Interceptor isn't designed to handle, throw it again so subsequent error handlers, like those in your services, can pick up and handle non-401 errors for their own calls.

My Interceptor has a twofold job. It:

  1. Injects an Authorization header into all outgoing requests, if it has a token stored.
  2. Intercepts HTTP 401 Unauthenticated errors, at which point it invalidates the token store, then redirects back to /login.
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import {
    HttpErrorResponse,
    HttpEvent,
    HttpHandler,
    HttpInterceptor,
    HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';

import { AuthService } from './auth.service';

@Injectable({
    providedIn: 'root',
})
export class RequestInterceptor implements HttpInterceptor {

    constructor(
        private readonly auth: AuthService,
        private readonly router: Router,
    ) {
    }

    /**
     * @param HttpRequest<any> request - The intercepted request
     * @param HttpHandler next - The next interceptor in the pipeline
     * @return Observable<HttpEvent<any>>
     */
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        request = this.addToken(request);
        return next.handle(request)
            // add error handling
            .pipe(
                catchError(
                    (error: any, caught: Observable<HttpEvent<any>>) => {
                        if (error.status === 401) {
                            this.handleAuthError();
                            // if you've caught / handled the error, you don't
                            // want to rethrow it unless you also want
                            // downstream consumers to have to handle it as
                            // well.
                            return of(error);
                        }
                        throw error;
                    }
                ),
            );
    }

    /**
     * Handle API authentication errors.
     */
    private handleAuthError() {
        // clear stored credentials; they're invalid
        this.auth.credentials = null;
        // navigate back to the login page
        this.router.navigate(['/login']);
    }

    /**
     * Add stored auth token to request headers.
     * @param HttpRequest<any> request - the intercepted request
     * @return HttpRequest<any> - the modified request
     */
    private addToken(request: HttpRequest<any>): HttpRequest<any> {
        const token: string = this.auth.token;
        if (token) {
            return request.clone({
                setHeaders: {
                    Authorization: `Bearer ${token}`,
                },
            });
        }
        return request;
    }

}

All AuthService does is have a public get/set that sticks a credentials object into localStorage - it makes sure the token isn't expired, but you can design it however you want.

Like @firegloves stated above, you must add the Interceptor to the pipeline in app.module.ts:

import { RequestInterceptor } from './auth/request.interceptor';

@NgModule({
    declarations: [],
    imports: [],
    exports: [],
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: RequestInterceptor,
            multi: true,
        },
    ],
    bootstrap: [AppComponent],
})
export class AppModule { }
amphetamachine
  • 27,620
  • 12
  • 60
  • 72
1

sample error handling in angular 7

public getstats(param: string, confgId: number, startDate: Date, endDate: Date): Observable<string> {
return this.http.post(this.stats, {
  param: param,
  endDate: endDate
}).pipe(tap((stats: string) => console.log('Got Exceedance Stats : ' + JSON.stringify(stats))),
  catchError(this.handleError<string>('stats')));

}

Below is the error handler

private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
  console.error(error);   
  console.log(`${operation} failed: ${error.message}`);
  return of(result as T);
};

}

Yvonne Aburrow
  • 2,602
  • 1
  • 17
  • 47
ranki
  • 11
  • 1