#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
, когда я могу составить тот же результат с помощью селекторов.