9

I have a hot Observable fed by a socket. I can use the pausable to pause the socket feed. But once I 'unpause' the observable, I need to display the last values that the socket could have sent while the subscription was paused. I don't want to keep track of the last values the socket sends manually. How could this be pausible?

From the example in the documentation, see comments below:

var pauser = new Rx.Subject();
var source = Rx.Observable.fromEvent(document, 'mousemove').pausable(pauser);

var subscription = source.subscribe(
    function (x) {
        //somehow after pauser.onNext(true)...push the last socket value sent while this was paused...
        console.log('Next: ' + x.toString());
    },
    function (err) {
        console.log('Error: ' + err);
    },
    function () {
        console.log('Completed');
    });

// To begin the flow
pauser.onNext(true); 

// To pause the flow at any point
pauser.onNext(false);  
Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Thibs
  • 8,058
  • 13
  • 54
  • 85

2 Answers2

8

You don't even need pausable to do this. (Note as well that you tagged RxJS5 but pausable only exists in RxJS 4). You simply need to convert your pauser into a higher order Observable:

var source = Rx.Observable.fromEvent(document, 'mousemove')
  // Always preserves the last value sent from the source so that
  // new subscribers can receive it.
  .publishReplay(1);

pauser
  // Close old streams (also called flatMapLatest)
  .switchMap(active => 
    // If the stream is active return the source
    // Otherwise return an empty Observable.
    Rx.Observable.if(() => active, source, Rx.Observable.empty())
  )
  .subscribe(/**/)

//Make the stream go live
source.connect();
paulpdaniels
  • 18,395
  • 2
  • 51
  • 55
  • 2
    Thanks for replying Paul, but I am new to rxjs and sorry that I don't fully understand the above example. Where does the pauser and active come from and how does it relate to the source? I don't see the connection... thank-you – Thibs Dec 12 '16 at 18:27
  • 1
    @Thibs `pauser` is the same as the `Subject` you created, it emits true or false values based on if the stream should be paused or not. If true it returns the *hot* stream from the socket (or in this case a mouse move) and if not it emits nothing (hence paused), every time it is unpaused it will emit the last event received from the socket during the pause ( if there was one) – paulpdaniels Dec 12 '16 at 19:19
  • 1
    Tip: if you put `Observable.empty()` where T is the type of `source` then the subscription will get the correct type. If you just put `Observable.empty` then it cannot determine a common type. For instance I'm using this code but the source event is a number so I have `.switchMap(enabled => Observable.if(() => enabled, this._orderId, Observable.empty())).subscribe((orderId) => ...)` and the compiler makes orderId a `number` by itself – Simon_Weaver Jan 14 '18 at 01:17
0

Since many of the used functions are deprecated or gone by now, here is a version using rxjs 7.5.5

const pauser = new BehaviorSubject(false);

source$.pipe(
   shareReplay({ bufferSize: 1, refCount: false }), // make sure last emitted value is replayed when observable is resumed
   takeUntil(pauser.pipe(filter((isPaused) => isPaused === true))),
   repeat({ delay: () => pauser.pipe(filter((isPaused) => isPaused === false)) }),
),

The only thing I am not happy about with this solution is the shareReplay + refCount: false, because I'm afraid it will cause memory leaks.
Does anyone have solution that doesn't require shareReplay + refCount: false?
Or can explain to me how to remove / clear observables using it, when I am sure I will no longer need them?

EDIT: You can omit shareReplay, when source$ is based on a Behavior- or ReplaySubject, which replay the last value on (re)subscribing anyway.

EDIT2: Actually this is not a good solution, since it immeditaly resubscribes to the observable on completion. This causes endless-loops, if subscribed to a observable created by of. This is due to the immediate execution of repeat and not only after takeUntil triggered.
You would have to introduce a flag that is set to true, after takeUntil and back to false after repeat.
Instead I found this solution more readable, which is pretty much the accepted answer xD

export function pauseWhileInactive<T>(
  isActive$: Observable<boolean>
): (source$: Observable<T>) => Observable<T> {
  return (source$) =>
    source$.pipe(
      switchMap(() =>
        isActive$.pipe(
          switchMap((isOn) => (isOn ? source$ : EMPTY))
        )
      )
    );
}

const isActive$ = new BehaviorSubject(true);
obs$.pipe(pauseWhileInactive(isActive$))
Tucaen
  • 91
  • 1
  • 9