Есть ли лучший способ для меня кэшировать HTTP-запросы в Angular?

#angular #rxjs

#angular #rxjs

Вопрос:

Итак, я работаю над проектом Angular, в котором многие HTTP-ответы используются на нескольких страницах, поэтому я хотел кэшировать результаты во внешнем интерфейсе, чтобы их можно было использовать для разных компонентов.

Вот «простой» пример моей текущей реализации:

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;
      }
    });
  

Используя этот фрагмент, я могу поделиться одними и теми же результатами API на нескольких страницах, и я могу очистить кеш, выполнив

 this.service.serviceData.next(false)
  

В этом простом примере все выглядело нормально (все еще интересно узнать, есть ли лучший способ сделать это).

Момент, когда это становится очень сложным, — это когда мне нужно использовать результат одной подписки в качестве параметра для 2 кэшированных HTTP-вызовов. Например, если у меня есть beneficiaryList и beneficiaryCategoryList, которые оба зависят от выбранного в данный момент профиля, это то, что я получаю.

     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.
      }
    });
  

Есть ли более простой способ сделать это?
Это «решение» кажется очень излишним и запутанным.

Я был бы серьезно признателен за любые советы здесь, чтобы упростить мой текущий способ ведения дел, потому что этот проект оказывается чем-то вроде птичьего гнезда.

РЕДАКТИРОВАТЬ: я создал stackblitz с копией моей реализации.

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

О, еще один недостаток этой реализации, который я хотел бы исправить, заключается в том, что вам нужно очистить кеш обеих наблюдаемых, если вы хотите обновить хотя бы 1 из них, из-за того, что я использую zip. Но если я не буду использовать zip, реализация полностью прервется, потому что, как только завершится 1 вызов API, он снова запустит оба вызова.

Комментарии:

1. В вашем this._paymentSaService.selectedProfile.pipe блоке подписки кажется, что вы всегда попадаете в if (!categories || !beneficiaries) {} блок, поскольку вы устанавливаете this._paymentSaService.beneficiaryCategoryList и this._paymentSaService.beneficiaryListData на false при каждом выпуске данных (что кажется правильным), но я не понимаю, тогда почему у вас здесь есть else блок. При такой настройке вы всегда будете запрашивать новые данные? Наверное, я не уверен, как этот скрипт на самом деле полагается на какие-либо кэшированные данные.

2. @VidalQuevedo Я прикрепил stackblitz, если это поможет вам понять, как это работает. Я хочу повторять вызов каждый раз, когда изменяется выбранный профиль. Потому что beneficiaryCategoryList и beneficiaryListData зависят от текущего selectedProfile . Цель кэширования заключается в том, чтобы другие компоненты не выполняли одни и те же вызовы снова, все они считывались из одной и той же службы

3. я, вероятно, все упрощаю, но, похоже, может быть, вы все усложняете? не можете ли вы просто «кэшировать» значение в сервисе. вы можете сделать это, просто поместив это в любую переменную, поскольку сервис является одноэлементным.. а затем попросите все компоненты ссылаться на значение в сервисе (или подписаться на наблюдаемое, которое возвращает сервис), и все готово.

4. @JBoothUA Я действительно надеюсь, что это я все усложняю. Я бы хотел, чтобы работало более простое решение. Я был бы очень признателен, если бы вы могли разветвить мой stackblitz и реализовать то, что вы имеете в виду. Пожалуйста, обратите внимание, что важно, чтобы я мог очищать кеш по требованию, и это должно снова запустить вызов, чтобы получить последние данные. Например, допустим, у меня есть список задач, которые я извлекаю из HTTP и кэширую, чтобы другие компоненты могли считывать из того же значения, тогда я могу отправить запрос на добавление нового TODO, который очистит кэш, а затем он должен снова запросить API.

Ответ №1:

Изучая ваш код, я пошел дальше и немного упростил ситуацию. По сути, поскольку ваша цель — просто обновлять (и генерировать) кеши для beneficiaryList and beneficiaryCategoryList каждый раз, когда выбирается новый профиль, я попытался упростить все, переместив всю логику в subscribe блок и удалив операторы switchMap and zip .

Кроме того, я добавил a, forkJoin чтобы вы могли лучше контролировать, когда выполняются ваши запросы, поскольку в исходном сообщении ответы на ваши http-запросы запускались независимо, поэтому вы никогда не сможете получить ответ от обоих одновременно, на случай, если вам нужно, чтобы они что-то сделалиостальное со всеми данными сразу.

 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);
    })
  );
}  

Я разветвил ваш образец stackblitz и внес свои правки здесь: https://stackblitz.com/edit/angular-hnj3ys

Комментарии:

1. Спасибо за вашу готовность помочь. К сожалению, в этой реализации функции «обновления» не работают. Кэш очищается не только при изменении профиля, но и в других случаях, когда необходимо очистить кеш. Например, после добавления нового получателя я хочу иметь возможность очистить кеш и выполнить вызов снова, без необходимости снова изменять selectedProfile

2. Понял. Однако первоначально очистка кэшей, по-видимому, происходила каждый раз, когда выбирался профиль, что приводило к немедленному повторному заполнению запросов на их повторное заполнение: this._paymentSaService.beneficiaryCategoryList = new BehaviorSubject<BeneficiaryCategoryInterface[] | false>(false); this._paymentSaService.beneficiaryListData = new BehaviorSubject<Beneficiary[] | false>(false); Однако это не должно быть проблемой при добавлении нового профиля в предлагаемую повторную реализацию, поскольку новый профиль не будет categories или beneficiaries не будет связан с новым профилем. Просто мысль.

3. Да, требуемое поведение заключается в том, что при смене профиля кэш очищается, и он снова запускает запросы. Но, кроме того, мне также нужен способ вручную очистить кеш и снова запускать запросы без изменения профиля.

4. Хорошо, я обновил приведенный выше сценарий, чтобы перенести очистку и обновление категорий и получателей в отдельный метод updateBeneficiariesListAndCategories () , который вы можете использовать независимо на досуге (например, в update vs add profile events).