Skip to content

Instantly share code, notes, and snippets.

@alecmce
Last active October 11, 2017 19:00
Show Gist options
  • Save alecmce/791a7a1f2b8e365b7bb453abd244a392 to your computer and use it in GitHub Desktop.
Save alecmce/791a7a1f2b8e365b7bb453abd244a392 to your computer and use it in GitHub Desktop.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/takeUntil';
/**
* I am learning rxjs. The most complicated part is understanding the
* most elegant way to combine two upstream observables when they play
* different roles to the downstream behavior. Almost all examples of
* combining upstream observables imagine that the two observables are
* merged.
*
* In this example I imagine three actors: an active stream; a mousemove
* stream; and an update subscriber function. I want the update to be
* called with the mousemove event, only when active is true.
*
* There are two strategies offered, based on my very 2 day experience
* of rxjs. Neither seems quite right to me. I currently prefer the
* strategyUsingNestedSubscribers. Is there a 'best' way in rxjs? What is
* it?
*/
const active = makeActive();
const mousemove = makeMouseMove();
type Update = (event: MouseEvent) => void;
function strategyUsingNestedSubscribers(update: Update) {
active
.filter((flag: boolean) => flag)
.subscribe(() => {
mousemove
.takeUntil(active)
.subscribe(update);
})
}
function strategyUsingCombineLatest(update: Update) {
mousemove
.combineLatest(active)
.filter(([e, flag]: [MouseEvent, boolean]) => flag)
.map(([e, flag]: [MouseEvent, boolean]) => e)
.subscribe(update);
}
/** A reference example for how an active stream may be generated. */
function makeActive(): Observable<boolean> {
return Observable.fromEvent(checkbox, 'change')
.map((e: Event) => checkbox.checked)
.distinctUntilChanged();
}
/** A reference example for how a mousemove stream may be generated. */
function makeMouseMove(): Observable<MouseEvent> {
return Observable.fromEvent(document, 'mousemove');
}
@alecmce
Copy link
Author

alecmce commented Jul 29, 2017

Thanks to both @staltz and @trxcllnt! Yes, I realized that the first strategy would be an antipattern because of the nesting, which is why I tried to come up with the second strategy. There are always many ways to shave a Yak! But, not all Yak-shavers are equal. Essentially strategyUsingCombineLatest was my clumsy attempt to write what you both did more elegantly with switchMap and withLatestFrom.

I agree that withLatestFrom is preferred to switchMap, because of the dependency relationship it expresses on mousemove and active. switchMap conceptually nests mousemove in a condition on active, even if it avoids explicit nesting the way my strategyUsingNestedSubscribers worked. It is in some ways imperative control flow, hiding in Yak's clothing.

@trxcllnt
Copy link

trxcllnt commented Jul 29, 2017

@alecmce one last thing worth mentioning: the switchMap approach will remove the DOM listener for the mousemove event while active = false (since it disposes the subscription to mousemove, and fromEvent removes the DOM listener on disposal), but withLatestFrom won't. This distinction is subtle and usually either is fine, but keeping the subscription open can contribute jank in large-ish apps with many DOM listeners active.

@alecmce
Copy link
Author

alecmce commented Jul 29, 2017

@trxcllnt, thanks for pointing it out. Yeah, another thing to keep in mind!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment