Search code examples
angularrxjsangular-servicesrxjs6

Angular service data caching with RxJS


I have an angular service that is specific to a type of data in my project. Right now it just passes everything straight trhough to a generic data service that handles the HTTP requests.

@Injectable({
  providedIn: 'root',
})
export class QuestionLibraryService {
  private readonly dataSvc = inject(DataService);

  getAll(): Observable<Array<IQuestionLibrary>> {
    return this.dataSvc.questionLibraryGetAll();
  }

  getOne(libraryId: string): Observable<IQuestionLibrary> {
    return this.dataSvc.questionLibraryGet(libraryId);
  }
  
  //create, delete, update, etc...
}

I want to cache the data received by this service to make fewer HTTP calls when navigating around the app, and to speed it up so that there aren't to many brief flashes of a loading state.

Here is what I have tried so far, and this works well for the getAll() method, but I'm not sure what to do about getOne().

private dataCache: Array<IQuestionLibrary> = [];

getAll(): Observable<Array<IQuestionLibrary>> {
  if (this.dataCache.length > 0) {
    return of(this.dataCache);
  }

  return this.dataSvc.questionLibraryGetAll().pipe(
    tap((libList) => {
      this.dataCache = libList;
    }),
  );
}

getOne(libraryId: string): Observable<IQuestionLibrary> {
  const found = this.getAll().pipe(
    map(list => list.find(item => item.id === libraryId))
  );

  //This is not right...
  if (found) {
    return found;
  }
}

The getOne() should get all of the items so they can be cached, this is a change from the current behavior where it calls a separate URL to get get a single item. I'm fine with abandoning that in favor of this.

However right now found is of type Observable<IQuestionLibrary | undefined> and I do not know hoe to check if an item was actually found or not since it's an observable.

I need it to either return the single found item, or to throw an error. How can I make it do this? Also, am I even on the right track here for how to cache data like this in a service?


Solution

  •   private refresh$ = new BehaviorSubject<void>(undefined);
    
      getAll$(): Observable<Array<IQuestionLibrary>> {
        return this.refresh$.pipe(
          switchMap(() => {
            return this.dataSvc.questionLibraryGetAll().pipe(
              // catch error when nothing found - works only when service http request throws also error when nothing found - you can use catchError() also in DataService
              catchError((error) => {
                return throwError(error);
              })
            );
          }),
          // returns always the last value - no other caching needed
          shareReplay(1)
        );
      }
    
      refresh() {
        this.refresh$.next();
      }
    

    catchError
    https://rxjs.dev/api/operators/catchError

    shareReplay https://rxjs.dev/api/operators/shareReplay Good post about shareReplay: https://careydevelopment.us/blog/angular-use-sharereplay-to-cache-http-responses-from-downstream-services