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;
    });
  }
3
  • You should use an interceptor (HttpInterceptor) instead of a service. Commented Jun 21, 2018 at 14:03
  • What are you doing inside of admin.user.component.ts? You seem to be handling the error there. Commented Jun 21, 2018 at 14:04
  • I, am calling http call from admin.user.component.ts file. Not handling error from that file. Updated the code please have a look Commented Jun 21, 2018 at 14:15

3 Answers 3

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 { }
Sign up to request clarification or add additional context in comments.

6 Comments

you must import of from rxjs. if you are using a ide it should suggest you
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 why are you saying so?
@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;
I got this to work. handleAuthError() should end with "throw err", not "throw error".
|
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 { }

3 Comments

how do i return the error object (but not throwing it) back to the API caller function?
@ZhuHang To handle non-401 errors, inside your .service or .component, add a .pipe(catchError((err, caught) => {})) to the http observable chain, and it should receive the error package.
but if im using await xxx.toPromise() , can I still get the error object?
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);
};

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.