KevinTale
KevinTale

Reputation: 1898

Dispatch NgRx actions only when some properties changes on Rxjs Polling

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

Answers (2)

Andrei Gătej
Andrei Gătej

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

Barremian
Barremian

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

Related Questions