使用 observables 跟踪 typeahead 列表中的当前位置
Track current position in typeahead list with observables
我正在使用 TypeScript 自动完成组件(Angular 2,尽管这不是问题的核心),我想通过 RxJs 中的可观察对象来管理大部分(或全部)它( 5.0.0-beta.8)。通过 up/down 箭头键操作时,我在跟踪建议列表中的当前项目位置时遇到问题:索引仍然停留在 0。
以下所有逻辑都放在一个单独的 class
中,它接收输入 observables 并产生要订阅的输出 observables(这就是为什么与 Angular 2 不严格相关)。客户端代码正确订阅输出可观察对象。
这是一些代码:
// 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();
}
}
想法是,每次发出新的匹配项(建议)列表时,列表中的当前索引应该开始一个新的 "sequence",可以这么说。这些序列中的每一个都从 0 开始,由于箭头 down/up 键而侦听 increments/decrements,通过注意不要超出 lower/upper 限制来累积它们。
开始一个新序列 对我来说它翻译成 switchMap
。但是使用这样的代码,控制台只显示:
length: 5
index: 0
和箭头 up/down 键根本没有被检测到(尝试在 arrowDownIndexChange$
上插入其他日志),所以没有更多的日志并且对最终组件没有影响。就像他们的可观察对象不再 订阅 一样,但据我所知 switchMap
应该订阅最新生成的序列和所有以前的 drop/unsubscribe。
只是为了尝试,我改用了 mergeMap
:在这种情况下会检测到箭头键,但当然问题是所有序列(由于之前设置的匹配项)都合并在一起并且它们值相互重叠。除了这是不正确的,有时匹配列表会变空,所以总有一个点当前索引序列总是保持在 0。这个序列与所有其他序列合并并重叠,给出索引卡在的最终结果0.
我做错了什么?
好的。显然我之前通过 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$
与 keyUp$
错误。将 map().switch()
替换为 switchMap()
不会以任何方式损害功能。
我正在使用 TypeScript 自动完成组件(Angular 2,尽管这不是问题的核心),我想通过 RxJs 中的可观察对象来管理大部分(或全部)它( 5.0.0-beta.8)。通过 up/down 箭头键操作时,我在跟踪建议列表中的当前项目位置时遇到问题:索引仍然停留在 0。
以下所有逻辑都放在一个单独的 class
中,它接收输入 observables 并产生要订阅的输出 observables(这就是为什么与 Angular 2 不严格相关)。客户端代码正确订阅输出可观察对象。
这是一些代码:
// 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();
}
}
想法是,每次发出新的匹配项(建议)列表时,列表中的当前索引应该开始一个新的 "sequence",可以这么说。这些序列中的每一个都从 0 开始,由于箭头 down/up 键而侦听 increments/decrements,通过注意不要超出 lower/upper 限制来累积它们。
开始一个新序列 对我来说它翻译成 switchMap
。但是使用这样的代码,控制台只显示:
length: 5
index: 0
和箭头 up/down 键根本没有被检测到(尝试在 arrowDownIndexChange$
上插入其他日志),所以没有更多的日志并且对最终组件没有影响。就像他们的可观察对象不再 订阅 一样,但据我所知 switchMap
应该订阅最新生成的序列和所有以前的 drop/unsubscribe。
只是为了尝试,我改用了 mergeMap
:在这种情况下会检测到箭头键,但当然问题是所有序列(由于之前设置的匹配项)都合并在一起并且它们值相互重叠。除了这是不正确的,有时匹配列表会变空,所以总有一个点当前索引序列总是保持在 0。这个序列与所有其他序列合并并重叠,给出索引卡在的最终结果0.
我做错了什么?
好的。显然我之前通过 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$
与 keyUp$
错误。将 map().switch()
替换为 switchMap()
不会以任何方式损害功能。