Pause RxJS stream based on a value in the stream

前端 未结 6 482
一生所求
一生所求 2021-01-03 23:56

I have a simple component with a single button that starts and pauses a stream of numbers generated by RxJS timer.

import { Component, OnInit } from \'@angul         


        
6条回答
  •  醉话见心
    2021-01-04 00:40

    I'm assuming that the desired behaviour is not related to getting the values that the timer emits per se, and that instead of pausing notifications to an ongoing stream (in your example, the timer continues even if we don't see the values being printed), it's okay to actually stop emitting when paused.

    My solution is inspired by the Stopwatch recipe

    The solution below uses two separate buttons for play and pause, but you can adjust this to taste. We pass the (ViewChild) buttons to the service in the ngAfterViewInit hook of the component, then we subscribe to the stream.

    // pausable.component.ts
      ngAfterViewInit() {
        this.pausableService.initPausableStream(this.start.nativeElement, this.pause.nativeElement);
    
        this.pausableService.counter$
          .pipe(takeUntil(this.unsubscribe$)) // don't forget to unsubscribe :)
          .subscribe((state: State) => {
            console.log(state.value); // whatever you need
        });
      }
    
    // pausable.service.ts
    import { Injectable } from '@angular/core';
    
    import { merge, fromEvent, Subject, interval, NEVER } from 'rxjs';
    import { mapTo, startWith, scan, switchMap, tap, map } from 'rxjs/operators';
    
    export interface State {
      active: boolean;
      value: number;
    }
    
    @Injectable({
      providedIn: 'root'
    })
    export class PausableService {
    
      public counter$;
    
      constructor() { }
    
      initPausableStream(start: HTMLElement, pause: HTMLElement) {
    
        // convenience functions to map an element click to a result
        const fromClick = (el: HTMLElement) => fromEvent(el, 'click');
        const clickMapTo = (el: HTMLElement, obj: {}) => fromClick(el).pipe(mapTo(obj));
    
        const pauseByCondition$ = new Subject();
        const pauseCondition = (state: State): boolean => state.value % 5 === 0 && state.value !== 0;
    
        // define the events that may trigger a change
        const events$ = merge(
          clickMapTo(start, { active: true }),
          clickMapTo(pause, { active: false }),
          pauseByCondition$.pipe(mapTo({ active: false }))
        );
    
        // switch the counter stream based on events
        this.counter$ = events$.pipe(
          startWith({ active: true, value: 0 }),
          scan((state: State, curr) => ({ ...state, ...curr }), {}),
          switchMap((state: State) => state.active
            ? interval(500).pipe(
              tap(_ => ++state.value),
              map(_ => state))
            : NEVER),
          tap((state: State) => {
            if (pauseCondition(state)) {
              pauseByCondition$.next(); // trigger pause
            }
          })
        );
      }
    
    }
    
    

提交回复
热议问题