RxJS 动态分配按钮的多个订阅
RxJS Multiple subscriptions for dynamically allocated buttons
假设我有一个 angular 2 组件,如下所示:
import { Component, AfterViewInit, ViewChildren, QueryList, ElementRef } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import {ApiService} from './my.service'
@Component({
selector: 'my-component',
template: `
<div class='button0' [ngClass]="{selected: selection === 0}" #button>Hello World</div>
<div class='button1' [ngClass]="{selected: selection === 1}" #button>Hello World</div>
`,
styles: [`
.selected {
color: red;
}
`],
providers: [ApiService]
})
export class MyComponent implements AfterViewInit {
selection = 0;
@ViewChildren('button') buttons: QueryList<ElementRef>;
buttonObservables:any[] = null;
constructor(private api: ApiService) {}
updateServer(index) {
api.requestsExample(index)
.then((result) => {
//update other divs and stuff
}
}
updateColor(index) {
this.selection = index;
}
ngAfterViewInit () {
this.buttonObservables = this.buttons.map((button) => Observable
.fromEvent<MouseEvent>(button.nativeElement, 'click'));
this.buttonObservables.map((observable) => {
observable.throttleTime(2000).subscribe((event:MouseEvent) => {
const element:Element = event.target as Element;
this.updateServer(element.classList[1].match(/\d+/g));
})
});
this.buttonObservables.map((observable) => {
observable.subscribe((event:MouseEvent) => {
const element:Element = event.target as Element;
this.updateColor(element.classList[1].match(/\d+/g));
})
});
}
}
其中 ApiService.requestsExample
是一个异步注释函数,它发出请求并 returns 响应。
代码几乎可以正常工作(例如,请求受到限制,按钮混搭不会导致太多请求,颜色仍然会改变)
我正在努力想办法处理以下边缘情况:
1)我想保证最后触发的结果是接受响应的结果(假设响应返回),然后按时间顺序返回。由于请求是异步的,我不确定如何实现这一点?
2)(推论)为了防止更新闪烁,我还想丢弃从服务器返回的任何结果,一旦后来的结果返回(基于问题顺序而不是响应顺序)。
3) 一旦最后一个当前实时请求 returns,我想丢弃所有正在进行的可观察对象,因为我不再关心它们。
所以基本上,如果用户按下两个按钮 20 秒,我预计会发出 10 次左右的请求,但除了切换按钮颜色外,更新 UI 一次,并更新到正确的价值。
此外,如果有任何关于是否有更好的方法使用 Observables 实现此结果的反馈(或者即使 Observables 是这项工作的正确工具!)
让我们解释下面的 RxJS 5 示例:
- 您想使
updateServer
成为反应计算的一部分,因此您使它 return 成为可观察的。
- 由于您以相同的方式处理所有点击,因此
mergeAll
来自不同按钮的所有点击是有意义的。
- 由于您仅使用按钮的索引进行计算,因此
map
次点击才有意义。
- 您可以直接
updateColor
作为 do
的副作用。
debounceTime(1000)
仅在一秒后没有其他点击后发出点击。我认为这比 Throttle
好,因为您不想在用户多次快速点击时进行不必要的网络调用。只有最后一个。
- 既然要在新的点击到来时取消之前的
updateServer
,那么使用switchMap
就有意义了。它将点击映射到新的 updateServer
observable 然后中止它并在新点击到达时切换到新点击。
- 因为您想忽略第一次
updateServer
之后的所有进一步点击,或者这就是我的理解 3),take(1)
将取一个结果然后完成整个链。
updateServer(index) {
return Observable.fromPromise(api.requestsExample(index))
}
this
.buttonObservables
.mergeAll()
.map(ev => ev.target.classList[1].match(/\d+/g))
.do(idx => this.updateColor(idx))
.debounceTime(1000)
.switchMap(idx => this.updateServer(idx))
.take(1)
.subscribe(result => {
// update other divs
})
假设我有一个 angular 2 组件,如下所示:
import { Component, AfterViewInit, ViewChildren, QueryList, ElementRef } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import {ApiService} from './my.service'
@Component({
selector: 'my-component',
template: `
<div class='button0' [ngClass]="{selected: selection === 0}" #button>Hello World</div>
<div class='button1' [ngClass]="{selected: selection === 1}" #button>Hello World</div>
`,
styles: [`
.selected {
color: red;
}
`],
providers: [ApiService]
})
export class MyComponent implements AfterViewInit {
selection = 0;
@ViewChildren('button') buttons: QueryList<ElementRef>;
buttonObservables:any[] = null;
constructor(private api: ApiService) {}
updateServer(index) {
api.requestsExample(index)
.then((result) => {
//update other divs and stuff
}
}
updateColor(index) {
this.selection = index;
}
ngAfterViewInit () {
this.buttonObservables = this.buttons.map((button) => Observable
.fromEvent<MouseEvent>(button.nativeElement, 'click'));
this.buttonObservables.map((observable) => {
observable.throttleTime(2000).subscribe((event:MouseEvent) => {
const element:Element = event.target as Element;
this.updateServer(element.classList[1].match(/\d+/g));
})
});
this.buttonObservables.map((observable) => {
observable.subscribe((event:MouseEvent) => {
const element:Element = event.target as Element;
this.updateColor(element.classList[1].match(/\d+/g));
})
});
}
}
其中 ApiService.requestsExample
是一个异步注释函数,它发出请求并 returns 响应。
代码几乎可以正常工作(例如,请求受到限制,按钮混搭不会导致太多请求,颜色仍然会改变)
我正在努力想办法处理以下边缘情况: 1)我想保证最后触发的结果是接受响应的结果(假设响应返回),然后按时间顺序返回。由于请求是异步的,我不确定如何实现这一点? 2)(推论)为了防止更新闪烁,我还想丢弃从服务器返回的任何结果,一旦后来的结果返回(基于问题顺序而不是响应顺序)。 3) 一旦最后一个当前实时请求 returns,我想丢弃所有正在进行的可观察对象,因为我不再关心它们。
所以基本上,如果用户按下两个按钮 20 秒,我预计会发出 10 次左右的请求,但除了切换按钮颜色外,更新 UI 一次,并更新到正确的价值。
此外,如果有任何关于是否有更好的方法使用 Observables 实现此结果的反馈(或者即使 Observables 是这项工作的正确工具!)
让我们解释下面的 RxJS 5 示例:
- 您想使
updateServer
成为反应计算的一部分,因此您使它 return 成为可观察的。 - 由于您以相同的方式处理所有点击,因此
mergeAll
来自不同按钮的所有点击是有意义的。 - 由于您仅使用按钮的索引进行计算,因此
map
次点击才有意义。 - 您可以直接
updateColor
作为do
的副作用。 debounceTime(1000)
仅在一秒后没有其他点击后发出点击。我认为这比Throttle
好,因为您不想在用户多次快速点击时进行不必要的网络调用。只有最后一个。- 既然要在新的点击到来时取消之前的
updateServer
,那么使用switchMap
就有意义了。它将点击映射到新的updateServer
observable 然后中止它并在新点击到达时切换到新点击。 - 因为您想忽略第一次
updateServer
之后的所有进一步点击,或者这就是我的理解 3),take(1)
将取一个结果然后完成整个链。
updateServer(index) {
return Observable.fromPromise(api.requestsExample(index))
}
this
.buttonObservables
.mergeAll()
.map(ev => ev.target.classList[1].match(/\d+/g))
.do(idx => this.updateColor(idx))
.debounceTime(1000)
.switchMap(idx => this.updateServer(idx))
.take(1)
.subscribe(result => {
// update other divs
})