1

How to combine the distinct, switchMap and mergeMap operators, so that when the source emits repeated values (detected by distinct.keySelector), the previous subscription is canceled (as in the switchMap), but if the value is not repeated follow the behavior of mergeMap?

Example:

source = from(1, 2, 1, 2, 3) // 'abcde'
result = source.pipe(delay(), combination() // '--cde'

I'm currently doing something like:

const activeSubscriptions = new Map();
source$.pipe(
  mergeMap((value) => {
    const pendingSubscription = activeSubscriptions.get(value);
    if (pendingSubscription) {
      pendingSubscription.unsubscribe();
      activeSubscriptions.delete(value);
    }
    const request$ = new Subject();
    const subscription = this.service.get(value).subscribe({
      complete: () => request$.complete(),
      error: (err) => request$.error(err),
      next: (value) => request$.next(value),
    });
    activeSubscriptions.set(value, subscription);
    return request$;
  })
);

But looking for a better way to do that.

Thank you in advance

Alex Parloti
  • 668
  • 1
  • 9
  • 25

1 Answers1

1

I think you can use the windowToggle operator for this:

src$ = src$.pipe(shareReplay(1));

src$.pipe(
  ignoreElements(),
  windowToggle(src$.pipe(observeOn(asyncScheduler)), openValue => src$.pipe(skip(1), filter(v => v === openValue))),
  mergeMap(
    window => window.pipe(
      startWith(null),
      withLatestFrom(src$.pipe(take(1))),
      map(([, windowVal]) => windowVal),
    )
  ),
)

A replacement for observeOn(asyncScheduler) could also be delay(0), the important thing is to make sure the order in which the src$'s subscribers receive the value is correct. In this case, we want to make sure that when src$ emits, the clean-up takes place first, so that's why we're using src$.pipe(observeOn(asyncScheduler)).

ignoreElements() is used because each window is paired to only one value, the one which has created the window. The first argument(s) passed to windowToggle will describe the observable(s) which can create the windows. So, we only need those, since we're able to get the last value with the help of

window => window.pipe(
  startWith(null),
  withLatestFrom(src$.pipe(take(1))),
  map(([, windowVal]) => windowVal),
)

By the way, a window is nothing but a Subject.

Lastly, if you want to perform async operations inside the window's pipe, you'll have to make sure that everything is unsubscribed when the window is completed(closed). To do that, you could try this:

window => window.pipe(
  startWith(null),
  withLatestFrom(src$.pipe(take(1))),
  map(([, windowVal]) => windowVal),
  switchMap(val => /* some async action which uses `val` */),
  takeUntil(window.pipe(isEmpty()))
)

where isEmpty will emit either true or false when the source(in this case, the window) completes. false means that the source had emitted at least one value before emitting a complete notification, and true otherwise. In this case, I'd say it's irrelevant whether it's true or false, since the window will not emit any values by itself(because we have used ignoreElements, which ignores everything except error and complete notifications).

Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31