Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)))
      );
    }
  }
}

like image 280
nop Avatar asked Aug 04 '21 17:08

nop


1 Answers

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.

like image 132
DeborahK Avatar answered Sep 19 '22 20:09

DeborahK