Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to restart rxjs interval?

Tags:

rxjs

I created a class which sets up a pausable RxJS Observable using the interval operator:

export class RepeatingServiceCall<T> {
  private paused = false;
  private observable: Observable<T>;
  constructor(serviceCall: () => Observable<T>, delay: number) {
    this.observable = interval(delay).pipe(flatMap(() => (!this.paused ? serviceCall() : NEVER)));
  }
  setPaused(paused: boolean) {
    this.paused = paused;
  }
  getObservable() {
    return observable;
  }
}

This seems to work fine, but the problem I am trying to solve is that I want the timer to reset when unpaused. So, let's say that the interval time is 10 seconds and 5 seconds after the last time the interval emitted, setPaused(false) is called. In that scenario, I want it to emit immediately and then restart the timer.

Would something like that be an easy thing to add?

like image 972
Lezorte Avatar asked Apr 16 '19 21:04

Lezorte


People also ask

What is RxJS interval?

RxJS interval() operator is a creation operator used to create an observable that emits a sequence of integers every time for the given time interval on a specified SchedulerLike. It emits incremented numbers periodically in time.

What is takeUntil in RxJS?

takeUntil passes values from the source observable to the observer (mirroring) until a provided observable known as the notifier emits its first value. The operator subscribes to the source observable and begins mirroring the source Observable. It also subscribes to the notifier observable.

How to emit sequence of values at 1 second interval in RxJS?

Example 1: Emit sequence of values at 1 second interval ( StackBlitz| jsBin| jsFiddle) 1 // RxJS v6+ 2 import{interval }from'rxjs'; 3 4 //emit value in sequence every 1 second 5 constsource =interval(1000); 6 //output: 0,1,2,3,4,5....

How do I stop a JavaScript interval from running immediately?

medium.com/js-in-action/… If you use timer instead of interval, and set the initial delay to 0, then your interval will fire immediately. You can use takeUntil operator to prevent the interval to run always, and repeatWhen operator to restart it whenever you want:

What is the replacement of repeatwhen() in RxJS?

From official documentation, repeatWhen () is deprecated in RxJs of v7 and will be removed in future version, and repeat () is a replacement of it.

What is the time to wait before starting the interval?

If a number, is the time to wait before starting the interval. If a Date, is the exact time at which to start the interval. The delay between each value emitted in the interval.


3 Answers

If you use timer instead of interval, and set the initial delay to 0, then your interval will fire immediately.

You can use takeUntil operator to prevent the interval to run always, and repeatWhen operator to restart it whenever you want:

import { Observable, Subject, timer } from 'rxjs';
import { repeatWhen, switchMap, takeUntil } from 'rxjs/operators';
    
export class RepeatingServiceCall<T> {
  readonly observable$: Observable<T>;
  private readonly _stop = new Subject<void>();
  private readonly _start = new Subject<void>();
    
  constructor(serviceCall: () => Observable<T>, delay: number) {
    this.observable$ = timer(0, delay)
      .pipe(
        switchMap(() => serviceCall()),
        takeUntil(this._stop),
        repeatWhen(() => this._start)
      );
  }
  start(): void {
    this._start.next();
  }
  stop(): void {
    this._stop.next();
  }
}

Here is a working StackBlitz example.

P.S.: Getters and setters are working different in typescript. So you do not need classic getter concept, you can just make the attribute public and readonly.

like image 63
s.alem Avatar answered Oct 23 '22 01:10

s.alem


You can achieve the behavior you are describing with the following snippet:

const delay = 1000;

const playing = new BehaviorSubject(false);

const observable = playing.pipe(
  switchMap(e => !!e ? interval(delay).pipe(startWith('start')) : never())
);

observable.subscribe(e => console.log(e));

// play:
playing.next(true);

// pause:
playing.next(false);
  • When the playing Observable emits true, the switchMap operator will return a new interval Observable.
  • Use the startWith operator to emit an event immediately when unpausing.
  • If you wish to have the interval start automatically when subscribing to the observable, then simply initialize the BehaviorSubject with true.

StackBlitz Example

like image 20
Sam Herrmann Avatar answered Oct 23 '22 02:10

Sam Herrmann


Yet another approach with a switchMap:

const { fromEvent, timer } = rxjs;
const { takeUntil, switchMap, startWith } = rxjs.operators;

const start$ = fromEvent(document.getElementById('start'), 'click');
const stop$ = fromEvent(document.getElementById('stop'), 'click');


start$.pipe(
  startWith(void 0), // trigger emission at launch
  switchMap(() => timer(0, 1000).pipe(
    takeUntil(stop$)
  ))
).subscribe(console.log);
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>

<button id="start">start</button>
<button id="stop">stop</button>

And a simpler one, that merges start and stop Observables to switch off them:

const { fromEvent, merge, timer, NEVER } = rxjs;
const { distinctUntilChanged, switchMap, mapTo, startWith } = rxjs.operators;

const start$ = fromEvent(document.getElementById('start'), 'click');
const stop$ = fromEvent(document.getElementById('stop'), 'click');


merge(
  start$.pipe(mapTo(true), startWith(true)),
  stop$.pipe(mapTo(false))
).pipe(
  distinctUntilChanged(),
  switchMap(paused => paused ? timer(0, 1000) : NEVER)
)
.subscribe(console.log);
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>

<button id="start">start</button>
<button id="stop">stop</button>

And another, even wierder approach, using repeat() :

const { fromEvent, timer } = rxjs;
const { take, concatMap, takeUntil, repeat } = rxjs.operators;

const start$ = fromEvent(document.getElementById('start'), 'click');
const stop$ = fromEvent(document.getElementById('stop'), 'click');


start$.pipe(
  take(1),
  concatMap(()=>timer(0, 1000)),
  takeUntil(stop$),
  repeat()
).subscribe(console.log);
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>

<button id="start">start</button>
<button id="stop">stop</button>

Just wanted to join this party :)

like image 38
kos Avatar answered Oct 23 '22 02:10

kos