Search code examples
angularrxjsrxjs6

RxJS BehaviorSubject cashing request from http call


I'm having hard time finding neet solution for cashing http request to BehaviorSubject. I wanted to have only single method for cashing and getting value. Example below:

@Injectable({
  providedIn: 'root'
})
export class DataStoreService {

  private readonly initialState: DataState = {
    data: string,
    loaded: false
  };

  private data$: BehaviorSubject<DataState> = new BehaviorSubject<DataState>(this.initialState);

  constructor(private dataService: DataService) { }

  getData$(id: string): Observable<string> {
    return this.data$.asObservable()
      .pipe(
        switchMap((state: DataState) => {
          if (!state.loaded) {
            this.dataService.getData$(id);
          } else {
            return of(state.data);
          }
        }),
        withLatestFrom(this.data$.asObservable()),
        map(([data, state]) => {
          if (!state.loaded) {
            this.data$.next({
              loaded: true,
              data
            });
          }
          return data;
        })
      );
  }
}

I haven't tested it yet (service not working), but looks ok for now. Anybody knows any better solution to have less piping and conditionals (if/else) in rxjs for this kind of usage?


Solution

  • Instead of a BehaviorSubject you could use the operator shareReplay which uses a ReplaySubject internally and replays the n most recent values to future subscribers.

    private data$: Observable<string>;
    
    getData$(id: string): Observable<string> {
      if (!this.data$) {
        this.data$ = this.dataService.getData$(id).pipe(shareReplay(1));
      } 
      return this.data$;
    }
    

    The first subscription to getData$ will call this.dataService.getData$. All later subscriptions will get the data from that first call replayed by data$.