将多个Observable与不同的操作/操作组合在一起

时间:2016-10-14 15:14:09

标签: angular rxjs rxjs5 reactive-extensions-js angular2-observables

我正在构建一个Angular2应用程序,所以我已经习惯了Observables和Reactive Extensions作为一个整体。我正在使用TypeScript和rxjs。

现在我有一个可观察的,或者如果你愿意的话,有一些对象的数组。让我们说人物对象。现在我有两个其他的Person对象流,并希望将它们组合在一起,这样我就得到了一个始终是最新的流:

var people$ = getPeople();                  // Observable<Person[]>
var personAdded$ = eventHub.personAdded;    // Observable<Person>;
var personRemoved$ = eventHub.personRemoved // Observable<Person>;

var allwaysUpToDatePeople$ = people$.doSomeMagic(personAdded$, personRemoved$, ...);

如果people-stream发出一个数组,比如5个人,之后personAdded-stream发出一个人,allPeople-stream将发出一个6的数组。 如果personRemoved-stream发出一个人,那么allPeople-stream应该发出一个Person-objects数组,而不是personRemoved-stream发出的那个。

是否有一种内置于rxjs中的方法来获取此行为?

2 个答案:

答案 0 :(得分:1)

您希望合并所有流(Ghostbusters样式),然后使用扫描运算符来计算状态。扫描操作符的工作方式类似于Javascript reduce。

这是一个演示......

const initialPeople = ['Person 1', 'Person 2', 'Person 3', 'Person 4'];

const initialPeople$ = Rx.Observable.from(initialPeople);

const addPeople = ['Person 5', 'Person 6', 'Person 7'];

const addPeople$ = Rx.Observable.from(addPeople)
            .concatMap(x => Rx.Observable.of(x).delay(1000)); // this just makes it async

const removePeople = ['Person 2x', 'Person 4x'];

const removePeople$ = Rx.Observable.from(removePeople)
                                              .delay(5000)
                                                .concatMap(x => Rx.Observable.of(x).delay(1000));

const mergedStream$ = Rx.Observable.merge(initialPeople$, addPeople$, removePeople$)

mergedStream$
  .scan((acc, stream) => {
        if (stream.includes('x') && acc.length > 0) {
            const index = acc.findIndex(person => person === stream.replace('x', ''))
            acc.splice(index, 1);
        } else {
            acc.push(stream);
        }
      return acc;
  }, [])
  .subscribe(x => console.log(x))

// In the end, ["Person 1", "Person 3", "Person 5", "Person 6", "Person 7"]

serialize()

您没有提及数据的结构。我使用“x”作为旗帜有点(很多)笨重且有问题。但我想您会看到如何修改扫描运算符以适合您的数据。

答案 1 :(得分:0)

我的建议是,您将action的想法包含在一个流中,然后可以将其合并并直接应用于Array

第一步是定义一些描述你的行为的函数:

function add(people, person) {
  return people.concat([people]);
}

function remove(people, person) {
  const index = people.indexOf(person);
  return index < 0 ? people : people.splice(index, 1);
}

注意:我们避免在适当的位置改变Array,因为它可能会产生无法预料的副作用。纯度要求我们创建数组的副本。

现在我们可以使用这些函数并将它们提升到流中,以创建一个发出函数的Observable

const added$ = eventHub.personAdded.map(person => people => add(people, person));
const removed$ = eventHub.personAdded.map(person => people => remove(people, person));

现在我们以以下形式获取事件:people => people其中输入和输出将是一组人(在此示例中简化为字符串数组)。

现在我们如何连接它?好吧,我们真的只关心在之后添加或删除这些事件我们有一个数组来应用它们:

const currentPeople = 

  // Resets this stream if a new set of people comes in
  people$.switchMap(peopleArray => 

    // Merge the actions together 
    Rx.Observable.merge(added$, removed$)

      // Pass in the starting Array and apply each action as it comes in
      .scan((current, op) => op(current), peopleArray)

      // Always emit the starting array first
      .startWith(people)
  )
  // This just makes sure that every new subscription doesn't restart the stream
  // and every subscriber always gets the latest value
  .shareReplay(1);

这种技术有几种优化取决于你的需求(即避免函数curry,或使用二进制搜索),但我发现上面相对优雅的通用情况。