Диспетчерское действие N раз, но только один действующий вызов API и объединение результата N раз - PullRequest
1 голос
/ 07 февраля 2020

У меня есть несколько экземпляров одного и того же компонента в приложении Angular. Каждый экземпляр компонента отправляет действие [Action] LoadData для загрузки данных из внешнего API. Полезная нагрузка действия содержит информацию о том, какой компонент отправил действие, чтобы иметь возможность хранить его в соответствующем соответствующем месте в store.

Выбранные данные будут одинаковыми каждый раз, независимо от того, какая другая полезная нагрузка включена в действие. Поскольку это так, мне не нужно выполнять несколько вызовов API для загрузки одних и тех же данных, поэтому я могу отменить любые предыдущие незавершенные вызовы с помощью switchMap.

Для этого у меня есть два эффекта, один для загрузки данных:

@Effect({ dispatch: false })
loadData$ = this.actions$.pipe(
    ofType<LoadData>(LOAD_DATA),
    switchMap((action) => { // This will cancel out any previous service call
        return this.service.loadData().pipe(
            tap(val => {
                console.log(`Loading data...`)
                this.dataSubject$.next(val) // When the data is fetched, emit on the subject
            })
        );
    })
);

и один для обработки отправленного действия в сочетании с данными

@Effect()
handleActionAndDataLoaded$ = combineLatest(
    this.actions$.pipe(
        ofType<LoadData>(LOAD_DATA)
    ),
    this.dataSubject$.asObservable()
).pipe(
    tap(_ => console.log(`All done`))
)

и где dataSubject$ - это Subject.

Вот тут-то и вступает для меня сложная часть. Мне нужно, чтобы второй эффект срабатывал n раз, если было отправлено действие LoadData n раз, а затем обрабатывать данные в сочетании с другими полезными нагрузками действий соответственно. Вместо этого я теперь получаю один триггер второго эффекта.

Чтение combineLatest и просмотр мраморных диаграмм - это, конечно, ожидаемое поведение.

На мраморных диаграммах я получаю что-то вроде

enter image description here

Вместо этого мне нужно что-то вроде ниже.

enter image description here

Я знаю, что есть другие способы решения всей проблемы путем изменения других вещей в архитектуре приложения, но это то, с чем я должен работать в данный момент и мне кажется интересным rxjs вопрос!

Как этого достичь? Какую комбинацию операторов мне не хватает?

1 Ответ

1 голос
/ 09 февраля 2020

Я предполагаю, что это должно выглядеть более похоже на первую диаграмму:

actions -1-2-3-----------
data    ---------B-------
result  ---------(1B2B3B)

В противном случае похоже, что вы ожидаете, что действия LoadData будут сопряжены с ранее извлеченными данными.

Вы можете создайте конечный автомат, используя слияние и сканирование:

const createEffectWithScan = (actions$: Observable<string>, subject$: Observable<string>) =>
  merge(
    actions$.pipe(map(x => ({ type: 'action', value: x }))),
    subject$.pipe(map(x => ({ type: 'data', value: x }))),
  ).pipe(
    scan(
      (a, b) => b.type === 'action'
        ? { data: null, toEmit: [], buffer: [...a.buffer, b.value] }
        : { data: b.value, toEmit: a.buffer, buffer: []  }
      , { data: null, buffer: [], toEmit: [] }
    ),
    filter(x => x.data !== null),
    concatMap(x => x.toEmit.map(a => a + x.data))
  );

Stackblitz

Как вы упомянули, это хорошее упражнение, но, вероятно, его нужно решить другим способом , Обязательное изучение ценностей с помощью предметов не является хорошим решением.

...