1

This question builds upon this one, where it is shown how to feed an Observable into a Subject. My question is similar, but I want to avoid making the Observable hot unless it is necessary, so that it's .pipe() doesn't run needlessly. For example:

const subject$ = new Subject();
const mouseMove$ = Observable.fromEvent(document, 'mousemove')
                             .pipe(map(it => superExpensiveComputation(it)));
mouseMove$.subscribe(n => subject$.next(n));

Because of the subscription, this will make mouseMove$ hot, and superExpensiveComputation will be run for every mouse move, whether someone is listening for it on subject$ or not.

How can I feed the result of mouseMove$ into subject$ without running superExpensiveComputation unneccessarily?

Patrick
  • 999
  • 1
  • 9
  • 21
  • What are you trying to achieve? and why you don't subscribe to `mouseMove$` directly? – Amer Oct 01 '21 at 11:37

2 Answers2

2

You can simply use tap instead of subscribe to pass emissions to your subject:

const mouseMove$ = fromEvent(document, 'mousemove').pipe(
  map(it => superExpensiveComputation(it)),
  tap(subject$)
);

Of course you still need to subscribe to mouseMove$ to make the data flow, but you don't need to have a subscription dedicated to passing the data to your subject.

However, you'll probably want to add share as not to repeat the expensive logic for multiple subscribers.

const mouseMove$ = fromEvent(document, 'mousemove').pipe(
  map(it => superExpensiveComputation(it)),
  share(),
  tap(subject$)
);

But... then in that case, do you really need a subject at all? Unless you are going to be calling .next() from somewhere else, you probably don't.

BizzyBob
  • 12,309
  • 4
  • 27
  • 51
  • 1
    Yes, I need the subject in order to call next from other places. The real code is complex, the question is as minimal as I could make it. I thought of using `tap`, but that feels so side-effecty and dirty, but I guess it's fine – Patrick Oct 01 '21 at 14:37
0

Because of the subscription, this will make mouseMove$ hot

Wrong. The subscription doesn't change behavior of observable.

Observable.fromEvent(document, 'mousemove') is already hot.

Here's naive cold version of it. The key takeaway, I have to resubscribe it everytime to get the latest data.

const { of } = rxjs;

const mouseMove$ = of((() => {
let payload = {clientX: 0};
document.addEventListener("mousemove", (ev) => payload.clientX = ev.clientX)
return payload;
})())

let subscriber = mouseMove$.subscribe(pr => console.log(pr));

const loop = () => {
  if(subscriber) {
    subscriber.unsubscribe();
  }
  
  subscriber = mouseMove$.subscribe(pr => console.log(pr));

  setTimeout(loop, 1000);
};

setTimeout(loop, 1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>

and superExpensiveComputation will be run for every mouse move, whether someone is listening for it on subject$ or not

Then you can get rid of map(it => superExpensiveComputation(it)) and move that lamda to subscription so it still executes on every mouse move but after it has been subscribed..

Józef Podlecki
  • 10,453
  • 5
  • 24
  • 50