DEV Community

Cover image for Queries for Observables: Crazy & Simple!
Kostia Palchyk
Kostia Palchyk

Posted on

Queries for Observables: Crazy & Simple!

In the previous post we explored API to select events from streams using RegExp-like syntax, e.g.:

const D = fromEvent(element, 'mousedown');
const M = fromEvent(document, 'mousemove');
const U = fromEvent(document, 'mouseup');

exec(
  'DM*U'         // <- regular expression
  , { D, M, U }  // <- streams that will be used
)
  .pipe()
  .subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

In this post we'll review it's functional API counterpart:

query(D, some(M), U) // select some Ms between Ds and Us
  .pipe()
  .subscribe(console.log)
Enter fullscreen mode Exit fullscreen mode

But first, we'll recap the thought process. If you're already familiar with the idea — hop here

tl;dr: package @ https://github.com/erql/rx-rql 📦

💡 Recap the idea

Imagine we need to implement a drag-n-drop behavior.

For that, we have three event streams: mousemove$, mousedown$, mouseup$. So we want to capture mousemove$ events after mousedown$ emitted and before mouseup$.

Let's draw a marble diagram of the event streams we have:

mousedown$  --o------------
mousemove$  -o-o-o-o-o-o-o-
mouseup$    ------------o--
Enter fullscreen mode Exit fullscreen mode
Every event stream is represented with separate line.
o stands for event on the stream. - is a separator to represent passing time

For better readability let's substitute all the os to respective letters of the streams:

mousedown$  --d------------
mousemove$  -m-m-m-m-m-m-m-
mouseup$    ------------u--
Enter fullscreen mode Exit fullscreen mode
'd' will now stand for mouse-down event, 'm' for mouse-move, 'u' for mouse-up

Now that we have distinct event names, we can simplify our diagram to a single line of events:

events$     -mdm-m-m-m-mum-
Enter fullscreen mode Exit fullscreen mode

Let's remove the time - signs as well, we don't them:

events$      mdmmmmmum
Enter fullscreen mode Exit fullscreen mode

Okay, to rephrase our task in terms of the new diagram: we need to capture the m events between d and u emissions.

🤔

Hmm...

"we need ms between d and u"...

Sounds familiar...

Ah! If that was a string, we could easily do it with a regular expression:

/dm*u/.exec('mdmmmum')
Enter fullscreen mode Exit fullscreen mode

Would give us the needed dmmmu without trailing mouse-move m events...

Right?

If only we had a library to select events from streams with regexes...

🚀 Solution

query(D, some(M), U)
  .pipe()
  .subscribe(console.log)
Enter fullscreen mode Exit fullscreen mode

Rx-RQL 📦 package provides following API to make such selections:

  • query(…) — root of your selection
  • A — select 1 emission from the stream
  • some(A) — select 0 to ∞ emissions from A
  • maybe(A) — select 0 or 1 emission from A
  • many(n,m)(A) — select from n to m emissions from A
  • mute(A) — select emission from A & mute it

And you can group them as you like:

  • some(A, some(B), mute(C)) — select as many emissions from: select as many Bs as possible between emissions from A and muted C

Here's how to create a simple drag-n-drop behavior using this package:

And here's a Mr. Potato-Head DnD 🥔 — a more sophisticated example based on this amazing article by @dailydevtips1 ! Thx, Chris 🙏

👋 Outro

Thank you for reading this article! Stay reactive and have a nice day 🙂

If you enjoyed reading — please, indicate that with ❤️ 🦄 📘 buttons

And in case you're not yet following me here and on twitter — then you've probably missed my recent experiments: Rx + Proxy, Rx Autorun, React + Rx

Now I'd love to hear your thoughts! 👂

Top comments (3)

Collapse
 
yellow1912 profile image
yellow1912

Look very cool. I will try it out.

Collapse
 
kosich profile image
Kostia Palchyk

Thanks! 😊

Be cautious: although it's well covered with tests, it's still very experimental!

If community would find this library useful, I'm thinking it could be extended with:

  • time-based queries, like "listen to A for 5 sec"
  • pipe-able queries, like query(A, many(B).pipe(…), C) that would be applied to particular group output

Anyhow, please ping me back here or on github if you have additional thoughts or ideas. Thx!

Collapse
 
yellow1912 profile image
yellow1912

I think it would be interesting to create predefined example to solve some common scenarios. Things like Drag and Drop, Hotkeys etc. I can see many possibilities here.