Измените параметр без изменения подписки

#angular #rxjs #ngrx

Вопрос:

Мой угловой компонент может использовать параметр запроса status для фильтрации отображаемых результатов. Он попадет в хранилище с помощью селектора, и это наблюдаемое this.items$ затем будет сохранено в компоненте.

Это код, который я в настоящее время написал:

 this.route.queryParams.pipe(
      takeUntil(this.destroy$)
    ).subscribe((params: Params) => {
      const status = params['status'];
      if (status) {
        this.items$ = this.store.select(selectItemsByStatus, {status: status});
      }
      else {
        this.items$ = this.store.select(selectItemsByStatus, {status: 'all'});
      }
    });
 

Несмотря на то, что это работает, мое чувство говорит о том, что это можно сделать по-другому, более логично, более реактивно, если хотите. Я не думаю, что мне нужно делать выбор снова и снова. Есть ли способ сохранить ОДИН выбор, который будет выдавать разные значения при изменении статуса?

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

1. в чем разница в возвращаемых данных? Если вы сделаете один выбор, подписка будет обновляться в течение всего срока действия сайта. Почему бы вам (вместо этого) не изменить селектор (в вашем редукторе/индексе), который обрабатывает этот фильтр. Действие уже возвращает данные в состояние предположительно. Или создайте новый выбор в своем редукторе для обработки возврата отфильтрованных данных.

2. Селектор имеет объект props и выполняет фильтрацию на основе свойства status. Я хочу изменить опору, чтобы селектор выдавал элементы, соответствующие фильтру.

Ответ №1:

Мое чувство говорит, что это можно сделать по-другому, более логично, более реактивно, если хотите

Вы абсолютно правы в этом — При построении наблюдаемого вам обычно нужно думать в противоположном направлении: вместо «эта вещь изменилась, мне нужно установить это значение» следует «Это значение, от чего оно зависит».

В вашем случае, похоже items$ , это зависит только от маршрута queryParams , поэтому мы можем построить такой поток:

 items$ = this.route.queryParams.pipe(
  switchMap(params => {
    const status = params['status'];
    if (status) {
      return this.store.select(selectItemsByStatus, {status: status});
    }
    else {
      return this.store.select(selectItemsByStatus, {status: 'all'});
    }
  })
);
 

Это также имеет то преимущество, что вам не нужно иметь дело с другой подпиской — поэтому я думаю takeUntil , что здесь это было бы излишним, потребитель этого потока может отказаться от подписки, когда это необходимо.

Если элементы$ зависят от большего количества вещей, вы можете объединить другие наблюдаемые объекты с помощью combineLatest, merge, concat и т. Д.

Правка: Обратите внимание, что это будет вызывать this.store.select каждый раз queryParams , когда что-то выдает, даже если статус не меняется. Но для этого есть простое решение:

 items$ = this.route.queryParams.pipe(
  map(params => params['status']),
  // Only emit if the value (status) changes
  distinctUntilChanged(),
  switchMap(status => {
    if (status) {
      return this.store.select(selectItemsByStatus, {status: status});
    }
    else {
      return this.store.select(selectItemsByStatus, {status: 'all'});
    }
  })
);
 

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

1. Спасибо за ваш ответ. Моя фактическая цель-настроить подписку на магазин так, чтобы я подписывался только один раз, и значения, выдаваемые при изменении фильтра, изменялись. Мне нравится ваша идея обратить вспять мыслительный процесс при работе с оберваблями здесь.

Ответ №2:

Вы можете использовать combineLatest() с двумя параметрами:

 statusFilter$ = new BehaviorSubject('');

items$ = combineLatest(
  this.store.select(selectItems),
  statusFilter$,
).pipe(
  map(([items, statusFilter]) => items.filter(item => item.status === statusFilter)), // ... or whatever
);
 

Затем происходит изменение фильтра состояния statusFilter$.next('status') . Возможно, в вашем случае вы могли бы просто подписаться на параметр запроса непосредственно в фильтре (но это также приведет к распространению полных уведомлений, так что, возможно, это не совсем то, что вам нужно).:

 this.route.queryParams.pipe(...).subscribe(statusFilter$);
 

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

1. Фильтр передается селектору в качестве реквизита. Таким образом, фильтрацию выполняет селектор, а не мой компонент. Есть ли способ объединить это?

2. Вы хотите изменить объект реквизита, в который уже передан this.store.select() ?

3. Да, я думаю, что это то, к чему все сводится. Возможно ли это вообще?

4. Вы хотите изменить один и тот же экземпляр объекта, и это никоим образом не вызовет ничего внутри селектора хранилища.

5. Это, вероятно, сработало бы только в том случае, если мой селектор использует фильтр, который также находится в хранилище, а selectItems селектор использует его в качестве дочернего селектора. Я читал, что в NgRx 12 реквизиты для селекторов в любом случае устарели, поэтому, возможно, было бы лучше найти решение, которое не использует реквизиты.

Ответ №3:

Вы подумали @ngrx/router-store об этом ? Это может быть излишним для того, что вам нужно, но если вы часто обращаетесь к состоянию маршрутизатора, это может стоить вашего времени.

Вы могли бы перенести большую часть логики на селектор, например:

 export const selectVisibleItems = createSelector(
    selectState,
    selectQueryParams, // this is provided by @ngrx/router-store
    (state, params) => 
         // Do your selecting of data here depending on the query params - just making something up here for an example
         state?.items?.filter(
             item => item?.status === (params?.['status'] ?? 'all')
)
 

Тогда в вашем компоненте вам даже не придется беспокоиться о передаче реквизитов вашим селекторам, которые, как вы правильно указали, устарели.

 items$ = this.store.select(selectVisibleItems);
 

Я всегда стараюсь вложить в селекторы как можно больше логики. С ngrx сайта:

При использовании функций createSelector и createFeatureSelector @ngrx/store отслеживает последние аргументы, в которых была вызвана ваша функция селектора. Поскольку селекторы являются чистыми функциями, последний результат может быть возвращен, когда аргументы совпадают, без повторного вызова функции селектора. Это может обеспечить преимущества в производительности, особенно в случае селекторов, выполняющих дорогостоящие вычисления. Эта практика известна как запоминание.

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