I recently encountered a particularly nasty bug related to this concept.
export class AuthService {
authtoken = new ReplaySubject<string>(1);
// imagine other things calling `next` to set authorization tokens
}
export class AuthHttpInterceptor implements HttpInterceptor {
constructor(private _auth: AuthService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this._auth.authtoken
.pipe(
mergeMap((jwt) => {
let areq = req.clone({setHeaders: {Authorization: 'Bearer ' + jwt}});
return next.handle(areq);
}));
}
}
export class OtherService {
constructor(private _http: HttpClient) {}
thingy(): Observable<Thingy> {
return forkJoin(this._http.get('/thing1'), this._http.get('/thing2').pipe(
mergeMap(things => frobulate(things))
);
}
}
export class Page {
thingy: Thingy;
constructor(private _svc: OtherService) {}
fetchThingy(): void {
this._svc.thingy().subscribe(thingy => this.thingy = thingy);
}
}
This is also related to the “diffuse coding bug” topic, because it’s the interceptor that unwittingly causes the bug. OtherService.thingy()
will never emit.
After several hours of head-scratching and narrowing, I realized that since the authtoken ReplaySubject
never completes, neither will any of the intercepted HTTP requests (although they will emit, which means I didn’t notice the interceptor bug when just simply subscribing to requests, only when attempting to forkJoin
multiple ones).
A solution is to add first()
to the beginning of the interceptor’s pipe.