Reputation: 1898
I have a Rxjs Polling effect like so :
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}),
])
)
)
);
How can I dispatch updateFoo
and updateBar
only when data.foo
or data.bar
change respectively?
I can improve this by using distinctUntilChanged
, doing so the actions wont trigger if data.stuff
changes, however, both actions still dispatch when either one changes.
...
repeat(),
distinctUntileChanged((prev, curr) => prev.foo === curr.foo && 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}),
])
I want to dispatch updateFoo
when data.foo
changes and updateBar
when data.bar
changes, knowing that data
has a lot of other properties that can change as time goes on.
Upvotes: 4
Views: 961
Reputation: 11979
I think this could be an approach:
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})),
),
)
)
)
)
);
The source
from multicast
's second argument is the instance of the Subject which has been declared as the first argument. By using multicast
, we can divide the problem into 2 other smaller problems, without redundantly subscribing to the source(that's why a Subject has been used).
Upvotes: 2
Reputation: 31125
You could try to use pairwise
operator to get the previous state and explicitly check for the previous value. I've used startWith(null)
to trigger the emission for the first value from the HTTP request. Without it, it won't start emitting until the second call
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 && data.foo.prev !== data.foo.current)
result.push(fromActions.updateFoo({foo: data.foo}))
if (data.bar.prev && data.bar.prev !== data.bar.current)
result.push(fromActions.updateBar({bar: data.bar}))
return result;
})
)
)
);
Note: I've not used NgRX and this is untested code.
Upvotes: 0