#angular #rxjs #ngrx
#angular #rxjs #ngrx
Вопрос:
У меня есть эффект опроса Rxjs следующим образом :
updateProductData$ = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.loadProduct),
switchMap(_) =>
this.http.get('endpoint').pipe(
delay(1000),
repeat(),
switchMap((data) => [
fromActions.updateFoo({foo: data.foo}),
fromActions.updateBar({bar: data.bar}),
])
)
)
);
Как я могу отправлять updateFoo
и updateBar
только тогда, когда data.foo
или data.bar
изменить соответственно?
Я могу улучшить это, используя distinctUntilChanged
, при этом действия не будут запускаться при data.stuff
изменении, однако оба действия по-прежнему отправляются при изменении любого из них.
...
repeat(),
distinctUntileChanged((prev, curr) => prev.foo === curr.foo amp;amp; prev.bar === curr.bar) // works but it fires both actions when either one changes
switchMap((data) => [
fromActions.updateFoo({foo: data.foo}),
fromActions.updateBar({bar: data.bar}),
])
Я хочу отправлять updateFoo
, когда data.foo
изменения и updateBar
когда data.bar
изменения, зная, что data
у этого есть много других свойств, которые могут меняться с течением времени.
Ответ №1:
Я думаю, что это может быть подход:
updateProductData$ = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.loadProduct),
switchMap(_) =>
this.http.get('endpoint').pipe(
delay(1000),
repeat(),
multicast(
new Subject(),
source => merge(
// concerning `foo`
source.pipe(
distinctUntilChanged((prev, crt) => prev.foo === crt.foo),
map(data => fromActions.updateFoo({foo: data.foo})),
),
// concerning `bar`
source.pipe(
distinctUntilChanged((prev, crt) => prev.bar === crt.bar),
map(data => fromActions.updateBar({bar: data.bar})),
),
)
)
)
)
);
Второй source
аргумент from multicast
— это экземпляр Subject, который был объявлен в качестве первого аргумента. Используя multicast
, мы можем разделить проблему на 2 другие меньшие проблемы, без избыточной подписки на источник (вот почему был использован Subject).
Комментарии:
1. эффект, похоже, не возвращает никаких действий, VSCode жалуется на это. Я что-то упустил?
2. @GreatHawkeye Я не понимаю, почему он не вернется. Помимо ошибок типа, есть ли какие-либо другие ошибки? Может быть, вы могли бы добавить некоторый код, связанный с этим?
3. Вы были правы, это была опечатка! Однако оба действия запускаются один раз в начале, затем все работает правильно: опрос продолжается до тех пор, пока не изменится foo или bar, после чего он отправляет действия. Есть идеи, почему действия запускаются в начале? Это очевидно, потому что в начале prev.foo === curr.foo верно, но почему?
4. Только одно замечание: многоадресная рассылка (новая тема (), селектор) == публикация (селектор)
Ответ №2:
Вы можете попробовать использовать pairwise
operator для получения предыдущего состояния и явно проверить предыдущее значение. Я использовал startWith(null)
для запуска эмиссии для первого значения из HTTP-запроса. Без этого он не начнет выдавать до второго вызова
stop$ = new Subject<any>();
poll$ = timer(0, 1000).pipe(
startWith(null), // <-- emit `[null, data]` for first emission
switchMap(this.http.get('endpoint')),
pairwise(),
map((data) => ({
foo: {prev: data[0].foo, current: data[1].foo},
bar: {prev: data[0].bar, current: data[1].bar}
})),
takeUntil(stop$)
);
updateProductData$ = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.loadProduct),
switchMap(_ =>
poll$.pipe(
switchMap((data) => {
let result = [];
if (data.foo.prev amp;amp; data.foo.prev !== data.foo.current)
result.push(fromActions.updateFoo({foo: data.foo}))
if (data.bar.prev amp;amp; data.bar.prev !== data.bar.current)
result.push(fromActions.updateBar({bar: data.bar}))
return resu<
})
)
)
);
Примечание: я не использовал NgRX, и это непроверенный код.