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:

  1. Ta en titt på visibilityChange$ og isVisible$. 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år document.visibilityState er lik 'visible'.

  2. Ta en titt på source$. Den vil sende ut en bokstav og deretter vente 1 sekund med hjelp av concatMap og interval med take(1) og gjøre denne prosessen til det ikke er flere tegn igjen i teksten.

  3. Ta en titt på pausableInterval$. Basert på this.isVisible$ som vil endre seg i henhold til document.visibilityState, vil vår pausableInterval$ sende ut element per sekund eller ikke sende ut noe i det hele tatt på grunn av never().

  4. Ta en titt på lettersStream$. Med hjelp av zip() vil vi zippe pausableInterval$ og source$, slik at vi får en bokstav fra source og en tick fra pausable interval. Hvis pausableInterval$ 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.

Relaterte artikler