title: Using rxjs forkjoin and angular 10 http interceptor, how do I exchange only 1 refresh token with multiple http calls?
body: <p>I was researching how to do multiple http calls in Angular 10 that didn't require you to write a bunch of calls and subscribes that makes the code unnecessarily long. Stumbled across the rxjs forkjoin. Code is simple, just 4 https calls that I do stuff with when they return</p>
  <pre><code>const observableCall$ = this._configService.getSingle( &quot;Param1&quot;, 'Param2' ).pipe( catchError( err =&gt; of( err ) ) );
  const observableCall$ = this._configService.getSingle( &quot;Param1&quot;, 'Param2' ).pipe( catchError( err =&gt; of( err ) ) );
  const observableCall$ = this._configService.getSingle( &quot;Param1&quot;, &quot;Param2&quot; ).pipe( catchError( err =&gt; of( err ) ) );
  const observableCall$ = this._configService.getSingle( &quot;Param1&quot;, &quot;Param2&quot; ).pipe( catchError( err =&gt; of( err ) ) );
      forkJoin(
        [
          observableCall$,
          observableCall$,
          observableCall$,
          observableCall$,
        ]
      ).subscribe( ( results: Config[] ) =&gt; {
        //do stuff
      } )
  </code></pre>
  <p>The problem I'm running in to is with expired tokens. If the token is expired, the call to get a new token happens 4 times, which is obviously unnecessary.</p>
  <p>The code for the auth interceptor is this:</p>
  <pre><code>intercept( request: HttpRequest&lt;unknown&gt;, next: HttpHandler ): Observable&lt;HttpEvent&lt;unknown&gt;&gt; {
      const authToken = this._authentication.token;
      return ( !!authToken &amp;&amp; ( request.url.indexOf( environment.authBaseUrl ) === -1 )
        ? next.handle( request.clone( {
          headers: request.headers
            .set( 'Authorization', `${authToken.token_type} ${authToken.access_token}` )
        } ) ) : next.handle( request ) ).pipe( catchError( ( error ) =&gt; {
          if ( error instanceof HttpErrorResponse &amp;&amp; error.status === 401 ) {
            return this._authentication.exchangeRefreshToken()
              .pipe( mergeMap( token =&gt; next.handle( request.clone( {
                headers: request.headers.append( 'Authorization', `${token.token_type} ${token.access_token}` )
              } ) ) ) )
          }
        } ) )
    }
  </code></pre>
  <p>Code for exchanging refresh token:</p>
  <pre><code>const headers = this._httpHeaders;
      const body = new HttpParams()
        .append( 'grant_type', 'refresh_token' )
        .append( 'client_id', environment.clientId )
        .append( 'client_secret', environment.clientSecret )
        .append( 'refresh_token', this.token.refresh_token );
  
      var token = this._httpClient.post( environment.authBaseUrl, body, { headers } )
        .pipe( catchError( error =&gt; { return null; } ) ) as Observable&lt;Token&gt;;
      if ( !( token instanceof Token ) ) return this.getToken();
      else {
        this.token = new Token( token );
        return token;
      }
  </code></pre>
  <p>Now I think I understand the problem, which is the forkjoin is calling all 4 at the same time, they come through the interceptor as their own call and when the token is expired they all get a 401, therefore wanting a new token 4 times. Is there an elegant way to have it where it only asks for a new token once and then continue with the rest of the calls?</p>
  <p>Edit:
  I also tried just having those calls in the html with async pipes. I.E:</p>
  <pre><code>&lt;div *ngIf=&quot;observableCall$ | async&quot;&gt;{{ observableCall | json }}&lt;/div&gt;
      &lt;div *ngIf=&quot;observableCall$ | async&quot;&gt;{{ observableCall | json }}&lt;/div&gt;
  </code></pre>
  <p>and the same thing happened. If there's a better way to do that I'm open for suggestions.</p>
  <p>Thank you!</p>
  <p>Edit(2):
  Code in auth interceptor thanks to Mark's help</p>
  <pre><code>import {
    HttpErrorResponse,
    HttpEvent,
    HttpHandler,
    HttpInterceptor,
    HttpRequest
  } from '@angular/common/http';
  import { Injectable } from '@angular/core';
  import { merge } from 'lodash';
  import { Observable, of, Subject } from 'rxjs';
  import { catchError, exhaustMap, filter, first, mergeMap, share, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';
  import { Token } from 'src/app/models/token/token.model';
  import { environment } from 'src/environments/environment';
  import { AuthorizationService } from '../services/authorization/authorization.service';
  
  @Injectable()
  export class AuthorizationInterceptor implements HttpInterceptor {
    constructor( private _authentication: AuthorizationService ) { }
    private _exchangeToken$ = new Subject&lt;boolean&gt;();
  
    private _refreshToken$ = this._exchangeToken$.pipe(
      filter( x =&gt; x ),
      exhaustMap( () =&gt; this._authentication.exchangeRefreshToken() ),
      share()
    );
  
    private _refreshTokenCache$ = this._refreshToken$.pipe(
      startWith( null ),
      shareReplay( 1 )
    );
  
    exchangeRefreshToken( expiredToken: Token ): Observable&lt;Token&gt; {
      console.log( '2' );
      const exchange = () =&gt; {
        const startToken$ = of( true ).pipe(
          tap( x =&gt; this._exchangeToken$.next( x ) ),
          filter( _ =&gt; false )
        );
        return merge( startToken$, this._refreshToken$.pipe( first() ) );
      }
  
      return this._refreshTokenCache$.pipe(
        first(),
        switchMap( token =&gt;
          token == null || token === expiredToken ?
            exchange() :
            of( token )
        )
      );
    }
  
    intercept( request: HttpRequest&lt;unknown&gt;, next: HttpHandler ): Observable&lt;HttpEvent&lt;unknown&gt;&gt; {
      const authToken = this._authentication.token;
      return ( !!authToken &amp;&amp; ( request.url.indexOf( environment.authBaseUrl ) === -1 )
        ? next.handle( request.clone( {
          headers: request.headers
            .set( 'Authorization', `${authToken.token_type} ${authToken.access_token}` )
        } ) ) : next.handle( request ) ).pipe( catchError( ( error ) =&gt; {
          if ( error instanceof HttpErrorResponse &amp;&amp; error.status === 401 ) {
            return this.exchangeRefreshToken( authToken )
              .pipe( mergeMap( token =&gt; next.handle( request.clone( {
                headers: request.headers.append( 'Authorization', `${token.token_type} ${token.access_token}` )
              } ) ) ) );
          }
        } ) );
    }
  }
  </code></pre>