rxjsrxjs-observablesrxjs-pipeable-operators

RxJs: How to force execution of an unsubscribed observable


I am trying to create a TypeScript function that returns an observable, but I want it to execute its full set of pipe functionality, without always requiring the caller to subscribe to it.

My goal is to emulate the behaviour seen in libraries like NgRx Data, where the consuming application can invoke methods like getAll(), to query a remote server, and can either subscribe to consume the returned data or can simply ignore the response, knowing that NgRX will save it to the local state store.

The following function template works properly only if the user subscribes to it:

loadData(): Observable<MyEntity[]> {
  return this.http.get<MyEntity[]>('https://example.com/api/entities').pipe(
    tap(data => {

      // Save the retrieved data to local storage 

    })
  );
}

Alternatively, this approach always executes the intended side effect, but it never returns any data:

loadData(): Observable<MyEntity[]> {
  this.http.get<MyEntity[]>('https://example.com/api/entities').pipe(
    tap(data => {

      // Save the retrieved data to local storage 

    })
  ).subscribe();

  return of([]);
}

Is there a way, to combine these approaches to achieve unconditional execution, regardless of whether a subscription exists?


Solution

  • An observable is a function that sets up for observation, it is the act of subscribing that executes that function. Without a subscription there is no execution. NgRx is subscribing to to any observables in it's effects to save the data in the store.

    If you want to be able to tap into a stream to cause a side effect you have to subscribe or else the there is no execution of the pipe. You could use a shareReplay so the observable is executed but further subscriptions don't cause it to execute again.

    loadData(): Observable<MyEntity[]> {
      const request$ = this.http.get<MyEntity[]>('https://example.com/api/entities').pipe(
        tap(data => {
    
          // Save the retrieved data to local storage 
    
        }),
        shareReplay(1);
      );
    
      request$.subscribe();
    
      return request$;
    }
    

    This will cause the http request to be made instantly, your side effect will run inside the tap when the request completes. Any further subscriptions will get the same response without having to make the http request again and without triggering the side effect again.