问题描述
一个关于 RxJS 中间隔的简单问题 - 我制作了一个轮播图,幻灯片每分钟都在变化,有一个细节让我不满意。当用户手动将幻灯片切换到下一个或上一个时 - 间隔仍会自动更改幻灯片,但无论何时,直到间隔触发新值,因此它会忽略用户所做的任何事情。如何让间隔知道用户是否更改了幻灯片,并且仅从用户操作算一分钟,然后自动更改幻灯片。其他时间间隔应该自动正常工作。
很高兴听到想法,谢谢!!!
这是我得到的:
nodeGreen.getPeds(),p->node.contains(p.getX(),p.getY())
模板:
source = interval(10000);
ngOnInit(): void {
this.source.subscribe(() => this.slide('forward'));
}
slide(option): void {
switch (option) {
case 'forward':
this.currentSlide === this.SLIDES.length - 1 ?
this.currentSlide = DEFAULT_SLIDE : this.currentSlide++;
break;
case 'back':
this.currentSlide === DEFAULT_SLIDE ?
this.currentSlide = this.SLIDES.length - 1 : this.currentSlide--;
break;
}
}
解决方法
我会说 RxJS 对于这种行为来说太过分了。可以使用普通的 JS setTimeout()
函数来实现。此外,您不会在组件被销毁时关闭订阅,这可能会导致内存泄漏。
选项 1:JS setTimeout()
timeoutId: number;
ngOnInit(): void {
this.resetTimer(); // <-- start timer when the component is created
}
resetTimer() {
if (!!this.timeoutId) { // <-- Update: clear the timeout
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => this.slide('forward'),10000);
}
/**
* Reset the timer each time `slide()` function is triggered:
* 1. When the `setTimeout()` triggers the callback
* 2. When the user changes the slide manually
*/
slide(option): void {
this.resetTimer(); // <-- reset timer here
switch (option) {
case 'forward':
this.currentSlide === this.SLIDES.length - 1 ?
this.currentSlide = DEFAULT_SLIDE : this.currentSlide++;
break;
case 'back':
this.currentSlide === DEFAULT_SLIDE ?
this.currentSlide = this.SLIDES.length - 1 : this.currentSlide--;
break;
}
}
ngOnDestroy() {
clearTimeout(this.timeoutId); // <-- clear the timeout when the component is destroyed
}
选项 2:RxJS interval()
如果你坚持使用 RxJS,你需要考虑很多事情
- 使用
ReplaySubject
等多播 observable(此处显示)或使用带有fromEvent
函数的模板引用变量(未显示)获取用户的手动输入。 - 使用
switchMap
运算符将一个可观察对象映射到另一个可观察对象。switchMap
在外部 observable 发出时取消/关闭内部 observable。这样计时器就会重新生成。 - 在
ngOnDestroy()
挂钩中关闭订阅以避免打开订阅导致内存泄漏。此处使用takeUntil
运算符进行说明。
控制器 (*.ts)
import { ReplaySubject } from 'rxjs';
import { map,switchMap,takeUntil } from 'rxjs/operators';
clickEvents$ = new ReplaySubject<string>(1); // <-- buffer 1
closed$ = new ReplaySubject<any>(1);
ngOnInit() {
this.clickEvents$.asObservable.pipe(
switchMap((event: string) => interval(10000).pipe( // <-- cancel inner subscription when outer subscription emits
map(() => event) // <-- map back to the event
)),takeUntil(this.closed$)
).subscribe(this.slide.bind(this)); // <-- use `bind()` to preserve `this` in callback
}
slide(option): void {
switch (option) {
case 'forward':
this.currentSlide === this.SLIDES.length - 1 ?
this.currentSlide = DEFAULT_SLIDE : this.currentSlide++;
break;
case 'back':
this.currentSlide === DEFAULT_SLIDE ?
this.currentSlide = this.SLIDES.length - 1 : this.currentSlide--;
break;
}
}
ngOnDestroy() {
this.closed$.next(); // <-- close open subscriptions
}
模板 (*.html)
<button (click)="clickEvent$.next('back')">Back</button>
<button (click)="clickEvent$.next('forward')">Forward</button>
,
使用 RxJS 合并流
一种可能的解决方案是从按钮点击事件创建流并将它们合并在一起。
然后您可以使用 switchMap 每次源发出时重置间隔。这可能是这样的。
<div class="sliderNav">
<mat-icon #slideBack>keyboard_arrow_up</mat-icon>
<p>{{currentSlide + 1}}/{{SLIDES.length}}</p>
<mat-icon #slideForward>keyboard_arrow_down</mat-icon>
</div>
intervalTime = 10000;
@ViewChild('slideBack',{static: true})
slideBack: ElementRef;
@ViewChild('slideForward',{static: true})
slideForward: ElementRef;
ngOnInit(): void {
const [first$,forward$,interval$] = [
timer(intervalTime),fromEvent(slideForward,'click'),interval(intervalTime)
].map(v => v.pipe(
mapTo("forward")
));
const backward$ = fromEvent(slideBack,'click').pipe(
mapTo("backward")
);
merge(first$,backward$).pipe(
switchMap(option => interval$.pipe(
startWith(option)
))
).subscribe(option => {
switch (option) {
case 'forward':
this.currentSlide === this.SLIDES.length - 1 ?
this.currentSlide = DEFAULT_SLIDE : this.currentSlide++;
break;
case 'backward':
this.currentSlide === DEFAULT_SLIDE ?
this.currentSlide = this.SLIDES.length - 1 : this.currentSlide--;
break;
}
})
}