Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular6 HttpClient catchError not working

Tags:

angular

rxjs

CatchError is completelly ignored in 401 response.

I have httpInterceptor that handles oauth2 authentication.

Relevant code is :

import { filter, take, switchMap, map, catchError } from 'rxjs/operators';
//ommited
if (authService.hasRefreshToken()) {
    return authService.doRefreshToken().pipe(switchMap(tokenResponse => {
            const accessToken = tokenResponse['access_token'];
            this.tokenSubject.next(accessToken);
            return <Observable<HttpEvent<any>>> next.handle(this.addToken(req, accessToken));
        }), catchError((err: any, caught: any) => {
            console.log(err)
            return Observable.throw(err);
        })
    )
}

AuthService class:

export class AuthService {
    doRefreshToken() {
        //ommited
        return this.httpClient.post(environment.baseUrl + this.tokenEndpoint, null, requestOptions).pipe(
            map(tokenResponse => {
                this.saveToken(tokenResponse);
                return tokenResponse;
            }),
            catchError((err: any, caught: Observable<Object>) => {
                //refreshing token failed (refrech token invalid or expired) redirect to login and wipe everything
                this.logout();
                return Observable.throw(err);
            }));
    }
}

Everything works fine for 200 response, but 401 error is completely ignored. Am i using this new catchError the wrong way ?

P.S. this piece of code was working just fine with plain old catch , but now when i migrated to angular6 pipe and catchError same stuff just doesnt work.

EDIT :

Breakpoint on catchError reveals

"SyntaxError: Unexpected end of input at AuthService.push../src/app/common/auth/auth.service.ts.AuthService.doRefreshToken

Actual response from the server is :

{
 "error" : "invalid_token",
 "error_description" : "Refresh token expired"
}

Headers :

 Request Method: POST
 Status Code: 401 
like image 727
SeaBiscuit Avatar asked Jun 10 '18 10:06

SeaBiscuit


People also ask

How do you perform error handling for HttpClient?

The basic way to handle errors in Angular is to use Angular's HttpClient service along with RxJS operators throwError and catchError. The HTTP request is made, and it returns the data with a response if anything wrong happens then it returns an error object with an error status code.

How do you handle errors when HTTP Fails?

When the error occurs in the HTTP Request it is intercepted and invokes the catchError . Inside the catchError you can handle the error and then use throwError to throw it to the service. We then register the Interceptor in the Providers array of the root module using the injection token HTTP_INTERCEPTORS .


1 Answers

I had the same issue of the catchError function being completely ignored. Since this simple and straightforward code used by @SeaBiscuit as described in the official Angular documentation did not work, I abandoned that approach to handling error responses and instead looked at HttpInterceptors. And that worked!

I was inspired by the guidance of Luuk Gruijs in his article Global HTTP error catching in Angular 4.3+ to add the ErrorInterceptor shown below to my code.

Admittedly, the interceptor below may not be a perfect, but the most important part for handling errors in interceptors boils down to this:

next.handle(request).pipe(tap(
  (event: HttpEvent<any>) => { },
  (error: any) => {
    // Handle errors here!
  }
))

Here's a more detailed glimpse at what I implemented:

export class ErrorInterceptor implements HttpInterceptor {
  constructor() { }

  intercept (request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (request.method !== 'GET') {
      return next.handle(request);
    }

    const subject = new AsyncSubject<HttpEvent<any>>();

    next.handle(request)
    .pipe(
      tap((event: HttpEvent<any>) => {
        // Let HttpResponses pass through interceptor without interaction
        if (event instanceof HttpResponse) {
          subject.next(event);
          subject.complete();
        }
      }, (error: any) => {
        if (error instanceof HttpErrorResponse) {
          const errorEvent = new HttpResponse({
            body: {
              message: error.error.message,
              status: error.status,
              statusText: error.statusText
            }
          });

          subject.next(errorEvent);
          subject.complete();
        }
      })
    ).subscribe();

    return subject;
  }
}

I also modified my service to accept that manually created response body:

return this.http.get<MyObjI | ErrorI>(url, {params});

... where ErrorI is:

export interface ErrorI {
  message: string;
  status: number;
  statusText: string;
}

Hopefully handling errors in an interceptor does the trick for you, too!

like image 152
Kabb5 Avatar answered Sep 23 '22 04:09

Kabb5