RxJs:可观察搜索结果的模式
RxJs: Pattern for observable search results
我的场景是一个带有搜索表单和结果列表的经典网页。我想将加载结果的行为封装在一个 Observable 中。
这是我目前在 TypeScript 中所做的事情:
function loadResults(query): Observable<T[]> {}
const results = new Subject<ResultEvent<T[]>>();
const results: Observable<ResultEvent<T[]>> =
form.valueChanges
.distinctUntilChanged()
.do(() => results.next(ResultEvent.pending()))
.switchMap(query => loadResults(query))
.subscribe({
next: (data: T[]) => results.next(ResultEvent.present(data)),
error: err => results.next(ResultEvent.failed(err)),
});
想法是 results
始终包含搜索的当前状态:pending
、present
或 failed
。查询变化时,结果设置为pending
,服务returns数据时,结果设置为present
.
我不喜欢这个解决方案的地方是对 subscribe()
的显式调用。我宁愿有一个简单的 Observable
可以订阅和取消订阅(例如,在 Angular 中使用 async
管道),而不创建显式订阅。 do
中的副作用似乎也很老套。
const results: Obserbable<ResultEvent<T[]>> =
form.valueChanges.distinctUntilChanged()
. /* here be dragons */;
感谢任何建议和想法!
我想你想要这样的东西:
const results$ = form.valueChanges
// This is up to you, but with user input it might make sense to
// give it just a little bit of time before we hit the server since
// most user input will be more than a single character.
//.debounceTime(100)
.distinctUntilChanged()
// Using switchMap guarantees that the inner observable will be
// cancelled if the input changed while we are still waiting for
// a result. Newer is always better!
.switchMap(query => loadResults(query)
// If we get data, we use it.
.map(results => ResultEvent.present(results))
// We catch errors and turn them into a failure event.
.catch(err => Observable.of(ResultEvent.failed(err)))
// Whatever happens, first things first.
.startWith(ResultEvent.pending())
);
顺便说一句,我也会考虑在其中添加一个 debounceTime
。
这是一个片段,您可以将其复制粘贴到 https://rxviz.com 中以查看实际效果(不幸的是,他们的分享 link 功能不再起作用)。确保将时间 window 设置为 10 秒左右。
const ResultEvent = {
pending: () => 'Pending',
failed: err => 'Error: ' + err,
present: data => 'Data: ' + data,
};
const loadResults = query => query === 2
? Rx.Observable.of(null).delay(500).switchMap(() => Rx.Observable.throw('Oops'))
: Rx.Observable.of(42).delay(500)
const input$ = Rx.Observable.timer(0, 2000).take(4);
input$.switchMap(query => loadResults(query)
.map(data => ResultEvent.present(data))
.catch(err => Rx.Observable.of(ResultEvent.failed(err)))
.startWith(ResultEvent.pending())
)
我的场景是一个带有搜索表单和结果列表的经典网页。我想将加载结果的行为封装在一个 Observable 中。
这是我目前在 TypeScript 中所做的事情:
function loadResults(query): Observable<T[]> {}
const results = new Subject<ResultEvent<T[]>>();
const results: Observable<ResultEvent<T[]>> =
form.valueChanges
.distinctUntilChanged()
.do(() => results.next(ResultEvent.pending()))
.switchMap(query => loadResults(query))
.subscribe({
next: (data: T[]) => results.next(ResultEvent.present(data)),
error: err => results.next(ResultEvent.failed(err)),
});
想法是 results
始终包含搜索的当前状态:pending
、present
或 failed
。查询变化时,结果设置为pending
,服务returns数据时,结果设置为present
.
我不喜欢这个解决方案的地方是对 subscribe()
的显式调用。我宁愿有一个简单的 Observable
可以订阅和取消订阅(例如,在 Angular 中使用 async
管道),而不创建显式订阅。 do
中的副作用似乎也很老套。
const results: Obserbable<ResultEvent<T[]>> =
form.valueChanges.distinctUntilChanged()
. /* here be dragons */;
感谢任何建议和想法!
我想你想要这样的东西:
const results$ = form.valueChanges
// This is up to you, but with user input it might make sense to
// give it just a little bit of time before we hit the server since
// most user input will be more than a single character.
//.debounceTime(100)
.distinctUntilChanged()
// Using switchMap guarantees that the inner observable will be
// cancelled if the input changed while we are still waiting for
// a result. Newer is always better!
.switchMap(query => loadResults(query)
// If we get data, we use it.
.map(results => ResultEvent.present(results))
// We catch errors and turn them into a failure event.
.catch(err => Observable.of(ResultEvent.failed(err)))
// Whatever happens, first things first.
.startWith(ResultEvent.pending())
);
顺便说一句,我也会考虑在其中添加一个 debounceTime
。
这是一个片段,您可以将其复制粘贴到 https://rxviz.com 中以查看实际效果(不幸的是,他们的分享 link 功能不再起作用)。确保将时间 window 设置为 10 秒左右。
const ResultEvent = {
pending: () => 'Pending',
failed: err => 'Error: ' + err,
present: data => 'Data: ' + data,
};
const loadResults = query => query === 2
? Rx.Observable.of(null).delay(500).switchMap(() => Rx.Observable.throw('Oops'))
: Rx.Observable.of(42).delay(500)
const input$ = Rx.Observable.timer(0, 2000).take(4);
input$.switchMap(query => loadResults(query)
.map(data => ResultEvent.present(data))
.catch(err => Rx.Observable.of(ResultEvent.failed(err)))
.startWith(ResultEvent.pending())
)