angular - RxJS 6 Pause eller bufre observable når siden ikke er aktiv
Så jeg har en strøm av, la oss si, bokstaver, og jeg trenger at alle bokstavene kommer i riktig rekkefølge for å sette dem sammen til et ord. Alt fungerer fint helt til brukeren bytter fane, minimerer nettleseren eller bytter applikasjon - oppførselen er nesten den samme som om jeg brukte setTimeout() - rekkefølgen blir rotete, elementer går tapt, osv. Jeg prøvde å oppnå målet mitt ved å bruke bufferWhen(), bufferToggle(), takeUntil(), publish() og connect(), men ingenting lyktes. Jeg vurderte også å bruke delayWhen, men det er utdatert og sannsynligvis ikke egnet da det stopper strømmen umiddelbart. Hvilke funksjoner bør jeg bruke og hvordan? Her er koden min:
export class MyComponent implements AfterViewInit {
private visibilityChange$ = fromEvent(document, 'visibilitychange').pipe(startWith('visible'), shareReplay({ refCount: true, bufferSize: 1 }));
private show$ = this.visibilityChange$.pipe(filter(() => document.visibilityState === 'visible'));
private hide$ = this.visibilityChange$.pipe(filter(() => document.visibilityState === 'hidden'));
public ngAfterViewInit() {
const lettersStream$ = zip( // add delay for each letter
from(['w', 'o', 'r', 'd']),
interval(1000))
// pause when hide$ fires, resume when show$
.pipe(map(([letter, delayTime]) => letter))
.subscribe(console.log);
}
}
Jeg lagde en demo på Stackblitz - alt jeg vil er å se (slutt å skrive når fanen er inaktiv) hvordan setningen skrives på skjermen.
Svar #1
Siden jeg har gjort en lignende pause/fortsett-greie i mitt RxJS Snake-spill, vil jeg hjelpe deg med ditt eksempel.
Ideen er å ha en interval(1000) som en sannhetskilde, noe som betyr at alt vil være basert på den. Så målet vårt blir å gjøre dette intervallet mulig å sette på pause, basert på det faktum at vi trenger å stoppe utsendelsen av hendelser når synlighet skjules, og fortsette når synlighet vises. Til slutt, for å gjøre ting enklere, kan vi bare slutte å lytte til kildeintervallet når synlighet skjules, og begynne å lytte igjen når synlighet vises. La oss nå gå til den nøyaktige implementeringen:
Du kan også leke med den modifiserte StackBlitz-demoen av koden på RxJS Pause Observable.
import { of, interval, fromEvent, timer, from, zip, never } from 'rxjs';
import { delayWhen, tap, withLatestFrom, concatMap, take, startWith, distinctUntilChanged, switchMap, shareReplay, filter, map, finalize } from 'rxjs/operators';
console.log('-------------------------------------- STARTING ----------------------------')
class MyComponent {
private visibilityChange$ = fromEvent(document, 'visibilitychange')
.pipe(
map(x => document.visibilityState),
startWith('visible'),
shareReplay(1)
);
private isVisible$ = this.visibilityChange$.pipe(
map(x => x === 'visible'),
distinctUntilChanged(),
);
constructor() {
const intervalTime = 1000;
const source$ = from('word or two'.split(''));
/** should remove these .pipe(
concatMap(ch => interval(intervalTime).pipe(map(_ => ch), take(1)))
);*/
const pausableInterval$ = this.isVisible$.pipe(
switchMap(visible => visible ? interval(intervalTime) : never()),
)
const lettersStream$ = zip(pausableInterval$, source$).pipe(
map(([tick, letter]) => letter),
).subscribe(letter => {
this.writeLetter(letter);
});
}
private writeLetter(letter: string) {
if (letter === ' ') letter = '\u00A0'; // fix for spaces
document.body.innerText += letter;
}
}
const component = new MyComponent();
Dette er nøyaktig kode fra StackBlitz, jeg kopierte den hit for å forklare bedre for deg.
La oss nå bryte ned interessante deler for deg:
Ta en titt på
visibilityChange$ogisVisible$. De er endret litt, slik at den første sender ut strengverdien'visible'eller'hidden'basert pådocument.visibilityState. Den andre sender ut true nårdocument.visibilityStateer lik'visible'.Ta en titt på
source$. Den vil sende ut en bokstav og deretter vente 1 sekund med hjelp avconcatMapogintervalmedtake(1)og gjøre denne prosessen til det ikke er flere tegn igjen i teksten.Ta en titt på
pausableInterval$. Basert påthis.isVisible$som vil endre seg i henhold tildocument.visibilityState, vil vårpausableInterval$sende ut element per sekund eller ikke sende ut noe i det hele tatt på grunn avnever().Ta en titt på
lettersStream$. Med hjelp avzip()vil vi zippepausableInterval$ogsource$, slik at vi får en bokstav fra source og en tick fra pausable interval. HvispausableInterval$slutter å sende på grunn av visibilitychange, vil zip også vente, siden det trenger at begge Observables sender samtidig for å sende hendelsen til subscribe.
Svar #2
litt forvirret på bruksområdet, men dette kan løse det:
først gjør dette i stedet:
private isVisible$ = this.visibilityChange$.pipe(
filter(() => document.visibilityState === 'visible'),
distinctUntilChanged()); // just a safety operator
gjør da dette:
const lettersStream$ = this.isVisible$.pipe(
switchMap((isVisible) => (isVisible)
? zip( // add delay for each letter
from(['w', 'o', 'r', 'd']),
interval(1000))
.pipe(map(([letter, delayTime]) => letter))
: NEVER
)
).subscribe(console.log);
bare bruk switchMap hver gang synligheten endres, abonner på kilden hvis synlig, ikke gjør noe hvis ikke.
Med dette kunstige eksempelet vil oppførselen være litt ustabil fordi from() alltid vil sende ut den samme sekvensen, men med en ekte ikke-statisk kilde bør det fungere som forventet.
