使用可观察量

时间:2016-06-07 08:35:03

标签: typescript angular reactive-programming rxjs rxjs5

我正在使用TypeScript自动完成组件(对于Angular 2,虽然这不是问题的核心),我想通过RxJs中的observable管理其中的大部分(或全部)(5.0。 0-beta.8)。通过向上/向下箭头键操作时,我在跟踪建议列表中的当前项目位置时遇到问题:索引仍然停留在0。

以下所有逻辑都放在一个单独的class中,它接收输入的observable并生成输出可观察量以供订阅(这就是为什么与Angular 2没有严格关系)。客户端代码订阅正确输出observable。

这里有一些代码:

// Component responsible for managing only the list of suggestions
// It receives inputs from text field and it produces outputs 
// as current index in list, when to hide list, etc.
class AutocompleteListDriver {
  currentIndex$: Observable<number>;
  doClose$: Observable<void>;
  // ...

  constructor(...
    matches$: Observable<string[]>, // list of suggestions matching text in field
    keyUp$: Observable<KeyboardEvent>, // keyup events from text field
    keyDown$: Observable<KeyboardEvent>, // keydown events from text field
    ...) {

    const safeMatches$ = matches$
      .startWith([]);  // start with a clear, known state internally

    // when list is empty, component is hidden at rest:
    // detect keys only when component is visible
    const isActive$ = safeMatches$
      .map(matches => matches.length !== 0);

    const activeKeyUp$ = keyUp$
      .withLatestFrom(isActive$)
      .filter(tuple => tuple[1]) // -> isActive
      .map(tuple => tuple[0]);   // -> keyboardEvent

    this.currentIndex$ = safeMatches$
      .switchMap(matches => {
        const length = matches.length;
        console.log('length: ' + length);

        const initialIndex = 0;

        const arrowUpIndexChange$ = activeKeyUp$
          .filter(isArrowUpKey)
          .map(_ => -1);

        const arrowDownIndexChange$ = activeKeyUp$
          .filter(isArrowDownKey)
          .map(_ => +1);

        const arrowKeyIndexChange$ = Observable
          .merge(arrowUpIndexChange$, arrowDownIndexChange$)
          .do(value => console.log('arrow change: ' + value));

        const arrowKeyIndex$ = arrowKeyIndexChange$
          .scan((acc, change) => {
            // always bound result between 0 and length - 1
            const index = limitPositive(acc + change, length);

            return index;

          }, initialIndex)
          .do(value => console.log('arrow key index: ' + value))
          .startWith(0);

        return arrowKeyIndex$;
      })
      .do(value => console.log('index: ' + value))
      .share();
  }
}

这个想法是每次发出新的匹配列表(建议)时,列表中的当前索引应该开始一个新的&#34;序列&#34; ,这样说。这些序列中的每一个都从0开始,由于箭头向下/向上键而监听增量/减量,通过注意不要超出下限/上限来累积它们。

要向我开始新序列,它会转换为switchMap。但是使用这样的代码,控制台只显示:

length: 5
index: 0

并且根本没有检测到向上/向下箭头键(尝试在arrowDownIndexChange$上插入其他日志),因此不再有日志,也不会影响最终组件。如果他们的observable不再订阅就好了,但据我所知switchMap应该订阅最新生成的序列并删除/取消订阅以前的所有序列。

尝试一下,我使用mergeMap代替:在这种情况下,检测到箭头键,但当然问题是所有序列(由于匹配时的先前时刻)被合并在一起并且它们的值重叠彼此。除了这个不正确之外,匹配列表有时会变空,所以总是当前索引序列始终保持为0的点。这个序列与所有其他序列合并并重叠,给出最终结果指数陷入0。

我做错了什么?

1 个答案:

答案 0 :(得分:3)

确定。显然我以前通过rxjs文档发现错误的结果(当然我在之后写下了这个问题,然后寻找更多的见解)。

switchMap似乎不适合此案例。我应该使用的是switch()。这不应该让人感到惊讶,来自.Net Reactive Extensions背景......但我不知道为什么语言之间的命名变化经常让我错了。

工作解决方案应该是:

this.currentIndex$ = safeMatches$
  // use map to generate an "observable of observables"
  // (so called higher-order observable)
  .map(matches => {
    const length = matches.length;
    // ...
  })
  // "unwrap" higher order observable, by always 
  // keeping subscribed to the latest inner observable
  .switch()
  .do(value => console.log('index: ' + value))

修改
这还不够。我还必须放弃activeKeyUp$,而选择原始keyUp$

如果有人有其他建议(或对当前行为的解释,例如activeKeyUp / keyUp),请随时回答。

编辑#2
感谢@paulpdaniels评论:事实证明唯一真正的问题是activeKeyUp$ Vs keyUp$错误。用map().switch()替换switchMap()并没有以任何方式损害功能。