Отправка действий NgRx только при изменении некоторых свойств при опросе Rxjs

#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. Только одно замечание: многоадресная рассылка (новая тема (), селектор) == публикация (селектор)

5. ncjamieson.com/calling-publish-with-a-selector

Ответ №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, и это непроверенный код.