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