Me encontré queriendo lograr algo como esto:

Dibujar círculos arrastrando el ratón con RxJS

Puedes ver la interacción terminada arriba y seguir la implementación paso a paso a continuación. La demostración alojada original y el proyecto fuente ya no están disponibles.

Lo que necesitaba hacer

  1. 😊 Importar RxJS
  2. 😉 Obtener los eventos desde el mouse
  3. 😎 Dibuja un círculo cuando el usuario arrastra el mouse.

Sencillo ¿verdad?

En realidad no

En realidad no.

1. Importar RxJS

Esto es bastante sencillo. Sólo tienes que encontrar una plataforma que te permita agregar RxJS de inmediato así:

Agregando RxJS en JS Bin

JS Bin

Y aunque JS Bin era perfecto para esto, quería usar Glitch, que también tenía muchas características interesantes. En este caso, tendrías que buscar RxJS en formato UMD e importarlo en tu index.html:

<script src="https://unpkg.com/@reactivex/rxjs@5.5.6/dist/global/Rx.js"></script>

Importando el paquete UMD RxJS en Glitch

Mira la línea #8.

2. Obtener los eventos desde el mouse.

Aquí es cuando RxJS empieza a brillar 🌟🌟🌟

Quizás ya lo sepas, pero puedes capturar fácilmente eventos del DOM usando el operador observable fromEvent():

const move$ = Rx.Observable.fromEvent(document, "mousemove");

move$.subscribe(console.log);

Que genera la siguiente información:

MouseEvent { isTrusted: true, screenX: 638, screenY: 373, ... }

Esta fue mi cara después:

Una reacción de sorpresa

De hecho tengo mejor cabello.

Una vez agregado el script canvas.js, se vería así:

move$.subscribe(drawCircle);

Lo cual dibujaría un círculo cada vez que muevo el mouse:

Dibujando un círculo por cada evento de movimiento del mouse

¡Que hubo un salto cuántico! 😱 ¿Qué hay dentro del guión del lienzo? Quizás te lo preguntes. Bueno, solo código de dibujo de Canvas. La fuente alojada original ya no está disponible, pero el flujo RxJS en el que nos centramos está todo aquí.

Nuestra aplicación dibuja círculos cada vez que movemos el mouse, pero necesitamos dibujar círculos solo cuando el usuario arrastra el mouse (haciendo clic y moviéndonos al mismo tiempo).

3. Dibuja un círculo cuando el usuario arrastra el mouse.

"¡Eh! Esto debería ser simple", dije.

Esto es lo que necesitaba:

  1. Crea observables para dos tipos más de eventos: mousedown y mouseup (para que podamos lograr el efecto de arrastre).
  2. Asegúrate de que las emisiones de nuestro primer observable (move$) comenzaron cuando se emitió el observable mousedown.
  3. Asegúrate de que las emisiones de nuestro primer observable (move$) se detuvieran cuando emitió el observable mouseup.

Sencillo ¿verdad?

En realidad no

En realidad no.

Mi primer enfoque para este problema fue utilizar los operadores RxJS skipUntil y takeUntil para lograr el comportamiento deseado.

Saltaríamos cada emisión de move$ hasta que se emitiera el observable down$. Y luego tomaría cada emisión de move$ hasta que se emitiera el observable up$. Así quedó:

const move$ = Rx.Observable.fromEvent(document, "mousemove");
const down$ = Rx.Observable.fromEvent(document, "mousedown");
const up$ = Rx.Observable.fromEvent(document, "mouseup");

move$.skipUntil(down$).takeUntil(up$).subscribe(drawCircle);

¡Y funcionó! 🎉

…

Solo una vez 😢😢😢

Resulta que cuando se aplica el operador takeUntil(), el observable se completa, lo que significa que no se recibirán más emisiones (después de que el usuario suelte el botón de clic).

Uhmmm 🤔🤔🤔

¡A menos que uses el operador repeat() para mantener viva la suscripción! 🎉🎉🎉

move$.skipUntil(down$).takeUntil(up$).repeat().subscribe(drawCircle);

¡Y funcionó! 🎉

…

De una manera muy horrible 😢😢😢

Si bien, en primer lugar, me permite mantener vivo el Observable, repite los eventos indefinidamente, lo que parece una exageración.

Para citar a Matthew Podwysocki:

“RxJS tiene tantas formas de obtener la misma respuesta”

Aquí fue cuando Matt y Cédric me echaron una mano. Me ayudaron a identificar una forma diferente de lograrlo usando el operador mergeMap().

Una primera reacción confusa a mergeMap

Mi primer vistazo.

Realmente no entendí cómo funcionaba hasta que vi este video y jugué con su jsfiddle: si escribes en el primer cuadro, no se emitirá nada hasta que escribas el segundo cuadro 👇👇👇

No queremos dibujar nada hasta que el usuario haya hecho clic. Entonces podemos usar el operador mergeMap aquí (que logra el mismo resultado que skipUntil arriba):

down$.mergeMap(() => move$).subscribe(drawCircle);

PERO esta vez el observable interno (también conocido como move$) se volverá a suscribir cada vez que el observable externo (también conocido como down$) emita.

Exactamente

¡Exactamente!

Eso significa que podemos utilizar el operador takeUntil de forma segura. ¡Porque lo observable en general seguirá vivo! 💚

down$.mergeMap(() => move$.takeUntil(up$)).subscribe(drawCircle);

Línea #10.

Sí, eso es todo. Para lograr arrastrar el mouse solo necesitas mergeMap y takeUntil 😉

Espero que esta publicación te haya sido útil. Si tienes alguna pregunta o comentario, déjalo en los comentarios. ¡Les respondo a todos! 👇👇👇

Gracias por leer. La implementación completa se incluye en los ejemplos anteriores.

Gracias a Matt y Cédric nuevamente, ¡fueron súper colaborativos y útiles!