I'm working on a website with authentication using JWT. I've created a HTTP interceptor class that adds the token to all requests headers and is used for catching 401 errors.
import {Injectable} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable, of} from 'rxjs';
import {JwtService} from '../service/jwt.service';
import {catchError} from 'rxjs/operators';
import {AlertService} from '../../shared/service/alert.service';
import {Router} from '@angular/router';
import {AlertType} from '../../shared/model/alert.model';
@Injectable()
export class HttpTokenInterceptor implements HttpInterceptor {
constructor(private jwtService: JwtService, private alertService: AlertService, private router: Router) {
}
/**
* Intercept HTTP requests and return a cloned version with added headers
*
* @param req incoming request
* @param next observable next request
*/
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Add headers to all requests
const headersConfig = {
'Accept': 'application/json'
};
// Add token bearer to header when it's available
const token = this.jwtService.getToken();
if (token) {
headersConfig['Authorization'] = `Bearer ${token}`;
headersConfig['Content-Type'] = 'application/json';
}
const request = req.clone({setHeaders: headersConfig});
// Return adjusted http request with added headers
return next.handle(request).pipe(
catchError((error: any) => {
// Unauthorized response
if (error.status === 401) {
this.handleError();
return of(error);
}
throw error;
})
);
}
/**
* Handle http errors
*/
private handleError() {
// Destroy the token
this.jwtService.destroyToken();
// Redirect to login page
this.router.navigate(['/login']);
// This is causing infinite loops in HTTP requests
this.alertService.showAlert({
message: 'Your token is invalid, please login again.',
type: AlertType.Warning
});
}
}
The class uses my JwtToken class to remove the token from the localstorage and redirect the user to the login page using the Angular Router. The showAlert method from the alertService is causing the http request to be repeated infinitely.
I think it's being caused by the Observer implementation in the alert service. But I've tried so many different implementations that I have really no idea what is going wrong.
import {Injectable} from '@angular/core';
import {Alert} from '../model/alert.model';
import {Subject} from 'rxjs';
/**
* Alert Service: Used for showing alerts all over the website
* Callable from all components
*/
@Injectable()
export class AlertService {
public alertEvent: Subject<Alert>;
/**
* AlertService constructor
*/
constructor() {
this.alertEvent = new Subject<Alert>();
}
/**
* Emit event containing an Alert object
*
* @param alert
*/
public showAlert(alert: Alert) {
this.alertEvent.next(alert);
}
}
The alertService class is being used by an alert component that displays all the alert messages. This component is used in two main components: Dashboard & login.
import {Component} from '@angular/core';
import {AlertService} from '../../shared/service/alert.service';
import {Alert} from '../../shared/model/alert.model';
@Component({
selector: '*brand*-alerts',
templateUrl: './alerts.component.html',
})
export class AlertsComponent {
// Keep list in global component
public alerts: Array<Alert> = [];
constructor(private alertService: AlertService) {
// Hook to alertEvents and add to class list
alertService.alertEvent.asObservable().subscribe(alerts => {
// console.log(alerts);
this.alerts.push(alerts);
});
}
}
In the following image is the issue clearly visible:
Kind regards.
Edit: solved
In the page that did a request there was a subscription initilialised on the alert service and that caused the http request to fire again. I simply have the alert component being the only subscriber to the alertService now and created a new service for the refresh. The answer from @incNick is indeed a correct implementation. Thanks!
Sorry of I'm busy on my job, but may my source make helpful.
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
...
return httpHandler.handle(request).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
//this.loadingService.endLoading();
}
},
(err: any) => {
//this.loadingService.endLoading();
}),
catchError((err: any) => {
if (err.status === 401) {
/*
this.modalController.create({
component: LoginComponent,
componentProps: {returnUrl: this.router.url},
showBackdrop: true
}).then(modal => modal.present());
*/
} else {
//this.messageService.showToast(`Some error happen, please try again. (Error-${err.status})`, 'error');
}
return throwError(err);
})
);
I'm return throwError(err) at end.