angulartypescriptrxjsangular12

throwError(error) is now deprecated, but there is no new Error(HttpErrorResponse)


Apparently throwError(error) is now deprecated. The IntelliSense of VS Code suggests throwError(() => new Error('error'). new Error(...) accepts only strings. What's the correct way to replace it without breaking my HttpErrorHandlerService ?

http-error.interceptor.ts

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpErrorResponse,
  HttpResponse,
  HttpHeaders
} from '@angular/common/http';
import { Observable, EMPTY, finalize, catchError, timeout, map, throwError } from 'rxjs';

import { HttpErrorHandlerService } from '@core/services';

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
  private readonly APP_XHR_TIMEOUT = 6000;

  constructor(private errorHandlerService: HttpErrorHandlerService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(this.performRequest(request)).pipe(
      timeout(this.APP_XHR_TIMEOUT),
      map((event: HttpEvent<any>) => this.handleSuccessfulResponse(event)),
      catchError((error: HttpErrorResponse) => this.processRequestError(error, request, next)),
      finalize(this.handleRequestCompleted.bind(this))
    );
  }

  private performRequest(request: HttpRequest<any>): HttpRequest<any> {
    let headers: HttpHeaders = request.headers;
    //headers = headers.set('MyCustomHeaderKey', `MyCustomHeaderValue`);
    return request.clone({ headers });
  }

  private handleSuccessfulResponse(event: HttpEvent<any>): HttpEvent<any> {
    if (event instanceof HttpResponse) {
      event = event.clone({ body: event.body.response });
    }
    return event;
  }

  private processRequestError(
    error: HttpErrorResponse,
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    console.log('http error response');

    if ([401].includes(error.status)) {
      return throwError(error);
    }

    this.errorHandlerService.handle(error);

    return throwError(error);
  }

  private handleRequestCompleted(): void {
    // console.log(`Request finished`);
  }
}

import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';

import { MessageService } from 'primeng/api';
import { TimeoutError } from 'rxjs';

/**
 * Shows a user-friendly error message when a HTTP request fails.
 */
@Injectable({
  providedIn: 'root'
})
export class HttpErrorHandlerService {
  constructor(private messageService: MessageService) {}

  handle(error: Error | HttpErrorResponse) {
    if (error instanceof TimeoutError) {
      return this.openDialog('error', `Няма връзка до сървъра.`);
    }

    if (error instanceof HttpErrorResponse && error.error && error.error.message) {
      return this.openDialog('error', error.error.message);
    }

    if (error instanceof Error) {
      switch (error.message) {
        default:
          return this.openDialog('error', `An unknown error occurred`);
      }
    }

    // Generic HTTP errors
    switch (error.status) {
      case 400:
        switch (error.error) {
          case 'invalid_username_or_password':
            return this.openDialog('error', 'Невалидно потребителско име или парола');
          default:
            return this.openDialog('error', 'Bad request');
        }

      case 401:
        return this.openDialog('error', 'Ще трябва да се логнете отново');

      case 403:
        return this.openDialog('error', `You don't have the required permissions`);

      case 404:
        return this.openDialog('error', 'Resource not found');

      case 422:
        return this.openDialog('error', 'Invalid data provided');

      case 500:
      case 501:
      case 502:
      case 503:
        return this.openDialog('error', 'An internal server error occurred');

      case -1:
        return this.openDialog(
          'error',
          'You appear to be offline. Please check your internet connection and try again.'
        );

      case 0:
        return this.openDialog('error', `CORS issue?`);

      default:
        return this.openDialog('error', `An unknown error occurred`);
    }
  }

  private openDialog(severity: string, message: string) {
    if (message?.trim()) {
      this.messageService.add({
        key: 'interceptor',
        severity: severity,
        summary: 'Информация',
        detail: message,
        life: 3000
      });
    }
  }
}

auth.interceptor.ts

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import {
  BehaviorSubject,
  catchError,
  EMPTY,
  filter,
  finalize,
  Observable,
  switchMap,
  take,
  throwError
} from 'rxjs';

import { AuthService } from '@core/services';
import { AuthResponse } from '@core/types';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private refreshTokenInProgress: boolean;
  private refreshToken$ = new BehaviorSubject<AuthResponse | null>(null);

  constructor(private authService: AuthService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next
      .handle(this.performRequest(request))
      .pipe(
        catchError((error: HttpErrorResponse) => this.processRequestError(error, request, next))
      );
  }

  private performRequest(request: HttpRequest<any>): HttpRequest<any> {
    const accessToken = this.authService.getAccessToken();

    let headers = request.headers;
    if (accessToken) {
      headers = headers.set('Authorization', `Bearer ${accessToken}`);
    }

    return request.clone({ headers });
  }

  private processRequestError(
    error: HttpErrorResponse,
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    console.log('auth interceptor called');

    if (
      error instanceof HttpErrorResponse &&
      error.status === 401 &&
      this.authService.isSignedIn()
    ) {
      return this.refreshToken(request, next);
    }

    return throwError(error);
  }

  private refreshToken(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('refresh token in auth.interceptor called');

    // in case the page consists of more than one requests
    if (!this.refreshTokenInProgress) {
      this.refreshToken$.next(null);
      this.refreshTokenInProgress = true;

      return this.authService.refreshToken().pipe(
        switchMap((response) => {
          if (response) {
            this.refreshToken$.next(response);
            return next.handle(this.performRequest(request));
          }

          this.authService.signOut();
          return throwError(() => new Error("RefreshTokenFailed"));
        }),
        catchError((error) => {
          this.authService.signOut();
          return throwError(error);
        }),
        finalize(() => (this.refreshTokenInProgress = false))
      );
    } else {
      // wait while getting new token
      return this.refreshToken$.pipe(
        filter((result) => result !== null),
        take(1),
        switchMap(() => next.handle(this.performRequest(request)))
      );
    }
  }
}


Solution

  • Instead of this:

        catchError((error) => {
          this.authService.signOut();
          return throwError(error);
        }),
    

    You could try this:

        catchError((error) => {
          this.authService.signOut();
          return throwError(() => error);
        }),
    

    I wasn't able to test it thoroughly, but a simple attempt seemed to work.

    This was my simple test (using RxJS v7.2):

    Service

      getProducts(): Observable<IProduct[]> {
        return this.http.get<IProduct[]>(this.productUrl)
          .pipe(
            tap(data => console.log('All: ', JSON.stringify(data))),
            catchError(this.handleError)
          );
      }
    
      private handleError(err: HttpErrorResponse): Observable<never> {
        // just a test ... more could would go here
        return throwError(() => err);
      }
    

    Notice that err here is of type HttpErrorResponse.

    Component

      ngOnInit(): void {
        this.sub = this.productService.getProducts().subscribe({
          next: products => {
            this.products = products;
            this.filteredProducts = this.products;
          },
          error: err => this.errorMessage = err.message
        });
      }
    

    Here I was able to retrieve the message property from the error response and display it in my UI.

    Let me know if this works for you.