Is there a better way for me to cache HTTP requests in Angular?

Viewed 121

So I work on a Angular project in which many of the HTTP responses are used on multiple pages, so I wanted to cache the results on the front end so that they can be shared across the different components.

Here is a a "simple" example of my current implementation:

Service.ts

serviceData = new BehaviorSubject(false);

Component.ts

    this.service.serviceData.pipe(takeUntil(this.unsubscribe$)).subscribe( response => {
      if (!responseData) {
        this.service.makeAPICall().subscribe(res => {
          this.service.serviceData.next(res);
        });
      } else {
        this.bankBranchDetails = bankData;
      }
    });

Using this snippet, I can share the same API results across multiple pages, and I can clear the cache by doing

this.service.serviceData.next(false)

In this simple example, things seemed fine (Still interested to know if there's a better way of doing this though).

The point where it becomes very complicated is when I need to use the result of one subscription as a parameter for 2 cached HTTP calls. For example, if I have a beneficiaryList and beneficiaryCategoryList which both depend on the currently selected profile, this is what I end up with.

    this._paymentSaService.selectedProfile.pipe(switchMap(selectedProfile => {
      this.selectedProfile = selectedProfile;

      //Clear the beneficiaryCategoryList and beneficiaryListData because the profile just changed, meaning we need to fetch the new results
      this._paymentSaService.beneficiaryCategoryList = new BehaviorSubject<BeneficiaryCategoryInterface[] | false>(false);
      this._paymentSaService.beneficiaryListData = new BehaviorSubject<Beneficiary[] | false>(false);

      return zip(this._paymentSaService.beneficiaryCategoryList, this._manageBeneficiary.beneficiaryList);
    }), takeUntil(this.unsubscribe$)).subscribe(([categories, beneficiaries]) => {
      if (!categories || !beneficiaries) {
        this._paymentSaService.getBeneficiaryCategories(this.selectedProfile.ProfileId).subscribe(res => {
          this._paymentSaService.beneficiaryCategoryList.next(res);
        });

        this._manageBeneficiary.getBeneficiariesList(this.selectedProfile.ProfileId).subscribe(res => {
          this._manageBeneficiary.beneficiaryList.next(res);
        });
      } else {
       //Both API calls completed, now we can handle the 2 pieces of data.
      }
    });

Is there a simpler way of doing this? This "solution" seems very overkill and confusing.

I would seriously appreciate any advice here to simplify my current way of doing things, because this project is turning out to be a bit of a birds nest.

EDIT: I have created a stackblitz with a replica of my implementation.

https://stackblitz.com/edit/angular-axpmm3?file=src/app/app.component.html

Oh, another downside of this implementation that I would love to fix, is that you have to clear the cache of both observables if you want to update even just 1 of them, due to me using a zip. But if I dont use a zip, the implementation would completely break because as soon as 1 API call finishes, it would kick off both calls again.

1 Answers

Looking into your code, I went ahead and simplified things a bit. Basically, since your goal is to just update (and emit) the caches for the beneficiaryList and the beneficiaryCategoryList every time a new profile is selected, I tried to streamline everything by moving all the logic into the subscribe block and removing the switchMap and zip operators.

In addition, I added a forkJoin so you can have more control over when your requests happen, since in the original post the responses from your http requests were being fired independently, so you'd never be able to have the response from both delivered at the same time, in case you needed them to do something else with all the data at once.

this.service.selectedProfile.pipe(
  takeUntil(this.unsubscribe$)
).subscribe((selectedProfile: any) => {
  this.selectedProfile = selectedProfile;

  /* Update beneficiaries and categories lists now 
  that the selectedProfile has changed.*/  
  this.updateBeneficiariesListAndCategories(selectedProfile).subscribe((res) => {
    // Do whatever you need with returned data
  });
});


/**
 * Place parallel requests to update beneficiaries and categoies
 * for a specific profile and update caches
 */
updateBeneficiariesListAndCategories(profile: any): Observable < any > {

  // Clear beneficiaries and categories 
  this.service.beneficiaryCategoryList.next([]);
  this.service.beneficiaryListData.next([]);

  // Create requests
  const getBeneficiaryCategoriesRequest = this.service
    .getBeneficiaryCategories(selectedProfile.ProfileId)
    .pipe(
      catchError(() => of ([]))
    );
  const getBeneficiariesListRequest = this.service
    .getBeneficiariesList(selectedProfile.ProfileId).pipe(
      catchError(() => of ([]))
    );

  /* Use a forkJoin to place both requests in parallel and
  then do something when both calls have completed (the
  catchError() operator on each request should keep the
  forkJoin from failing completely should either of the
  calls fail).*/
  return forkJoin({
    categories: getBeneficiaryCategoriesRequest,
    beneficiaries: getBeneficiariesListRequest
  }).pipe(
    tap((res: any) => {
      // Update caches 
      this.service.beneficiaryCategoryList.next(res.categories);
      this.service.beneficiaryListData.next(res.beneficiaries);
      console.log(res.categories);
      console.log(res.beneficiaries);
    })
  );
}

I forked your stackblitz sample and put my edits here: https://stackblitz.com/edit/angular-hnj3ys

Related