javascript - Återställ rxjs-intervall vid klick i Angular
En enkel fråga angående intervaller i RxJS - jag har gjort en karusell där bilder förändras varje minut och det finns en detalj som stör mig. När bilden byts manuellt av användaren till nästa eller föregående - kommer intervallet fortfarande att byta bilden automatiskt men på vilken som helst tid som återstår tills intervallet skickar ut det nya värdet, så det ignorerar allt som användaren gjorde. Hur kan jag få intervallet att veta om användaren ändrade bilden, och endast räkna en minut från användarens handling och sedan byta bilden automatiskt. Andra gånger ska intervallet fungera automatiskt som vanligt.
Blir glad att höra idéer, tack!!!
Här är vad jag fick:
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;
}
}
Mall :
<div class="sliderNav">
<mat-icon (click)="slide('back')">keyboard_arrow_up</mat-icon>
<p>{{currentSlide + 1}}/{{SLIDES.length}}</p>
<mat-icon (click)="slide('forward')">keyboard_arrow_down</mat-icon>
</div>
Svar #1
Jag skulle säga att RxJS är överdrivet för detta beteende. Det skulle kunna uppnås med vanlig JS setTimeout()-funktion. Dessutom stänger du inte prenumerationen när komponenten förstörs, vilket kan leda till en minnesläcka.
Alternativ 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
}
Alternativ 2: RxJS interval()
Om du insisterar på att använda RxJS, måste du tänka på flera saker
- Hämta manuella indata från användaren antingen med ett multicast-observable som
ReplaySubject(visas här) eller med en mallreferensvariabel medfromEvent-funktionen (inte visad). - Använd
switchMap-operatören för att mappa från en observerbar till en annan.switchMapavbryter/stänger den inre observerbara när den yttre observerbara emitterar. På detta sätt skulle timern återskapas. - Stäng prenumerationen i
ngOnDestroy()-hooken för att undvika minnesläckor från öppna prenumerationer. Illustreras här med hjälp avtakeUntil-operatorn.
Kontroller (*.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
}
Mall (*.html)
<button (click)="clickEvent$.next('back')">Back</button>
<button (click)="clickEvent$.next('forward')">Forward</button>
Svar #2
Sammanfoga strömmar med RxJS
En möjlig lösning är att skapa strömmar från dina knappklickhändelser och slå samman dem.
Du kan sedan använda switchMap för att återställa ditt intervall varje gång en källa emitterar. Så här kan det se ut.
<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$, forward$, 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;
}
})
}
