Here my setup... I keep my JWT Token in a NgRx store. Action & Effect are working fine to update the store with my token. Now a need to inject this token in my header thru an interceptor. The goal is that on the first API call, my token will be empty so I need to dispatch the action to obtain that token and inject it in the "current" API call. The problem is I can't find a way to bind the update of the data in the store to the ongoing request. The actual result is that this update arrived AFTER the next.handle occur and the API call occur without injection. This is my code :
@Injectable()
export class AuthTokenService implements HttpInterceptor {
myReq: HttpRequest<any>;
constructor(
private headerService: HeaderService,
private store: Store<TokenState>
) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// Don't intercept the getting of initial token
if (req.url !== environment.urlNewToken) {
// Check if we need a NEW Jwt token
this.store.select(selectJwt).subscribe((data) => {
if (data === '') {
// Dispatch "action" so "effect" will update store with new token
this.store.dispatch({ type: TokenActions.TokenAction.LoadKeyRing });
}
});
this.store
.select(selectKeyring)
.pipe(
skipWhile((keyring) => keyring.jwt.length === 0),
tap((keyring) => console.log(`the jwt value is : ${keyring.jwt}`)),
map((keyring) => keyring)
)
.subscribe((keyring) => {
this.myReq = req.clone();
this.myReq = this.headerService.addAuthToken(this.myReq, keyring.jwt);
});
return next.handle(this.myReq); // myReq don't have token
}
return next.handle(req);
}
}
I still try to wrap my head around Observable but obviously, I need more hints in real world situation! Thanks for your help :-)
UPDATE 2 This implementation work:
if (req.url !== environment.urlNewToken) {
return this.store.select(selectKeyring).pipe(
// update the token if needed
tap(
(keyring) =>
keyring.jwt === '' &&
this.store.dispatch({ type: TokenActions.TokenAction.LoadKeyRing })
),
switchMap((keyring) =>
this.store.select(selectKeyring).pipe(
// we skip the current value, since it will be the empty one
(keyring.jwt === '' && skip(1)) || identity
)
),
take(1),
switchMap((keyring) =>
next.handle(this.headerService.addAuthToken(req.clone(), keyring.jwt))
)
);
}
About update 2, I had to insert the take(1)
operator to stop the observable once the call was completed otherwise I entered into a racing condition as soon as the store changed state. All the call that was made before that started to "reappeared" and the app was re-sending those requests in infinite loop!
I think this could be an approach:
intercept (/* ... */) {
if (req.url !== environment.urlNewToken) {
return this.store.select(selectJtw)
.pipe(
map(data => data === ''),
// update the token if needed
tap(needsData => needsData && this.store.dispatch(/* ... */)),
switchMap(
needsData => this.store.select(selectKeyring).pipe(
// if we need to update the token first
// we skip the current value, since it will be the empty one
// otherwise, use an `identity` function(also available from rxjs as `identity`)
needsData && skip(1) || obs$ => obs$
)
),
switchMap(
keyring => next.handle(this.headerService.addAuthToken(req.clone(), keyring.jwt))
)
)
}
return next.handle(req);
}
If you'd like to read more about how interceptors work, I'd recommend having a look at this article.