8

I am trying to implement refresh tokens on my Angular2 app. I am taking an optimistic approach and instead of checking if the access token expired before making the request, I am making the request and if it returns a 401 code, I will refresh the access token by requesting a new one and saving it to local storage.

Here is my code snippet:

   getWithParams<T>(serviceUrl: string, params: URLSearchParams): Observable<T> {
    return super.getWithParams<T>(serviceUrl, params)
        .retryWhen((error) => {
            return error
                .filter((e) => e.status === 401)
                .scan((acc, value) => {
                    return acc + 1;
                }, 0)
                .takeWhile(acc => acc < 3)
                .flatMap(() => this.tokenRefreshService.refreshToken())
                .delay(1000);
        });
}

It is important to mention that super.getWithParams setsthe access token on the request headers by retrieving it from the local storage.

The method call tokenRefreshService.refreshToken() gets a new access token and saves it to local storage.

The problem that I am facing is that when the request is retried it is using the old access token, that is, it is no calling super.getWithParams to rebuild the request again. It just retries the exsiting observable.

Is there a way of building the request again? or chainging the request header of the observable that failed?

Radu Cojocari
  • 1,759
  • 1
  • 22
  • 25
  • This seems like a very similar to what you need http://stackoverflow.com/questions/39953419/get-new-ticket-then-retry-first-request/39955002#39955002, http://stackoverflow.com/questions/39797698/observable-continue-calling-api-and-changing-parameters-based-on-condition. Maybe also this http://stackoverflow.com/questions/41005674/angular-2-http-retrywhen/41014380#41014380 – martin Jan 16 '17 at 11:06

1 Answers1

7

Actually retryWhen() resubscribes to its source so you can use it to your advantage. This example should simulate your situation:

let token = 'token';
let counter = 0;

const source$ = Rx.Observable.defer(() => {
    console.log('Observable.defer(), token: ' + token);
    return Rx.Observable.of(token);
  })
  .map(token => {
    if (counter++ < 3) {
      throw new Error('invalid token');
    }
    return token;
  })
  .retryWhen((error) => {
    return error
      .filter(() => true) // or whatever...
      .do(() => token = token + 'bla'); // update the token
  })
  .map(token => { // create the request
    return "I'm a request with token: " + token;
  });


source$.subscribe(
  res => console.log(res),
  err => console.log('error: ' + err),
  () => console.log('complete')
);

See live demo: https://jsbin.com/roduqi/5/edit?js,console

This three times throws an error with invalid token and updates it every time.

Notice, that on every error I'm creating a new source Observable with Observable.defer.

This example print to console:

Observable.defer(), token: token
Observable.defer(), token: tokenbla
Observable.defer(), token: tokenblabla
Observable.defer(), token: tokenblablabla
I'm a request with token: tokenblablabla
complete
martin
  • 93,354
  • 25
  • 191
  • 226
  • Wrapping the super.getWithParam method call in an observable did the trick. Brilliant! – Radu Cojocari Jan 16 '17 at 11:55
  • Adding defer to my observable worked like charm. I was banging my head of 4 hours on this, thank you @martin! – Malathy Sep 24 '18 at 17:22
  • 1
    @martin can I use this in an angular interceptor in order to manipulate the http request? So basically return next.handle(request).pipe(retryWhen...? So basically if a request failed the first time, retry but add a param to the request to know its a retry as i need this info further down along the road. – Andreiasw Mar 12 '21 at 16:08
  • @Andreiasw have you found an answer to it? I'm asking this myself. I'm using an interceptor (`next.handle.......retryWhen()`) and want to modify the request from inside the retryWhen. The `source` in this case should be the `next.handle`, which I believe comes after the actual execution? Then it shouldnt be possible right? – C4d Sep 12 '22 at 06:11