Search code examples
angularrxjsangular-http-interceptors

Chronologically retry requests after restoring connection


How can I "stack" ongoing requests while user has no Internet connection and retry them chronologically after getting online again?

I can't use "pure" retryWhen in interceptor, because each intercept() call has no knowledge about other requests (I think).

I've tried to use interceptor to take requests during offline mode, add them to array (as Observables, something like this.offlineRequests.push(next.handle(request))) and finally resubscribe them again when connection is reestablished. BUT the problem is that my component doesn't get response. I mean requests are going out, but looks like component already get error and it doesn't care, that I subscribed next.handle() somewhere later.


Solution

  • I'd try something like this:

    offline.interceptor.ts

    class OfflineInterceptor implements HttpInterceptor {  
      constructor (private networkStatusService: NetworkStatusService) {}
      
      intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(req).pipe(
          catchError(err => {
            // At this point, we must return an observable.
            return concat(
              // We're first waiting for the connection to become available.
              // `ignoreElements` - will ignore `next` notifications. But it will let `error/complelete`
              // notifications pass through.
              this.networkStatusService.isOnline$.pipe(first(), ignoreElements()),
              // After we're back online, we make the request once again.
              next.handle(req),
            )
          })
        );
      }
    }
    

    network-status.service.ts

    class NetworkStatusService {
      private networkStatus = new Subject<boolean>();
    
      get isOnline$ () {
        return this.networkStatus.pipe(
          filter(Boolean)
        );
      }
    }
    

    So, when there is no internet connection, the subscription will be in a sort of pending state, until the connection re-establishes. Notice the first concat's argument:

    return concat(
      // !
      this.networkStatusService.isOnline$.pipe(first()),
      next.handle(req),
    )
    

    What essentially happens under the hood is the networkStatus Subject instance will have a new subscriber. And since this happens every time the request fails due to a poor connection, the order of the new subscribers will be respected. So, if we have something like this(in that order):

    // In `Component1` - this fails first.
    this.http.get(...);
    
    // In `Component2` - this fails second.
    this.http.get(...);
    
    // In `Component3` - this fails third.
    this.http.get(...);
    

    the networkStatus' subscribers will be(roughly - there is a bit more going on, but the concept still holds):

    // networkStatus.subscribers
    [
      SubscriberThatCorrespondsToComp1Subscriber,
      SubscriberThatCorrespondsToComp2Subscriber,
      SubscriberThatCorrespondsToComp3Subscriber,
    ]
    

    What this means is that when the connection comes back online, the requests will be retried based on their place in the networkStatus.subscribers array.