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:
For better readability let's substitute all the o
s to respective letters of the streams:
Now that we have distinct event names, we can simplify our diagram to a single line of events:
events$ -mdm-m-m-m-mum-
Let's remove the time -
signs as well, we don't them:
events$ mdmmmmmum
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
m
s betweend
andu
"...
Sounds familiar...
Ah! If that was a string, we could easily do it with a regular expression:
/dm*u/.exec('mdmmmum')
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...
🤩 Introducing Regular Expressions for Rx
With exprs-rx
package you can now query your streams with regular expressions like syntax!
mousedown$ --d------------
mousemove$ -m-m-m-m-m-m-m-
mouseup$ ------------u--
dm*u
result$ --dm-m-m-m-mu|
Benefits:
- clear and extensible syntax
- no need to remember dozens of operators
- no need to import them, which reduces bundle size
- with more implementations to come — one syntax for all stream libraries (
monetjs
,kefirjs
,baconjs
,TC39 Observables
, etc)
Example
Here's our drag-n-drop example in JS:
import { exec } from 'exprs-rx';
const item = document.getElementById('item');
const D = fromEvent(item, '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(
// ... apply any operators ...
)
.subscribe(console.log);
Repo //github.com/expressions-for-reactive-streams/js-rx-exprs
Currently, the library supports capital letters A-Z
that represent a single emission of a corresponding stream.
And a repeat A*
notation that will consume multiple events from the corresponding stream, until completion or until next entry in the regex matches.
And much more to come: ()
, []
, !
, etc!
For regex specification and plans on vocabulary, please, see this repo github.com/expressions-for-reactive-streams/spec-regular-expressions
Try it
Install it
npm i exprs-rx
🙌 Outro
The idea is to extend the expressions syntax to cover 80% of routine RxJS tasks. And maybe add combination commands. The possibilities are endless!
For upcoming amazing updates — follow me here and on twitter
If you've found this post worth sharing — please, use this tweet:
Top comments (5)
Whoa... RxJs and Reactive Programming can be hard. Regular Expressions can be hard. Seems like combining the two could make whatever your doing harder.
I don't consider regex to be clear if you don't use them regularly. (Which hopefully is most people hehe)
What would be a real-world use-case for getting
--dm-m-m-m-mu|
as a use case? Maybe testing?Hi, Brad!
Because I might've confused everyone with strings and regexes comparisons, just to be sure:
We don't get a string
"--dm-m-m-m-mu|"
. Marble diagrams I used are only for visualization of what's going on with Observables. We work with an actual live stream of events.When we apply a selector, what we get — is a stream of events, that match the selector.
For testing purposes, please see this amazing package rxjs-marbles by @cartant (it's actually used in this package testing flow)
Sorry, if that was obvious, just wanted to make sure we're on the same page 🙂
Now. To the main point:
Yep, both might be hard! And have you seen that regexp for email validation? Sheesh!
And Observables: I've been using RxJS for about 3 years now, have created a playground for observables and am currently working on a RxJS-based framework. Still I'm often confused with that toolset of operators:
Take the mouse Drag and Drop example: if I were to implement that in pure RxJS, I would start with this:
Then I'd realize that I'm doing a resubscription on
repeat()
, that could've been omitted. So I dig in my memory and under a thick layer of TV shows, I finally find awindowToggle
operator:Only I forget at first that it accepts a function as a second argument.
And then if later I were required to add those trailing
mouseDown$
andmouseUp$
events to the output stream w/o an additional subscription — ooff. That code grows fast.Now with a query expression, the same effect might be achieved with:
My point is that it's sometimes easier to use the declarative regex-like syntax.
And with library evolving — we'll get better options to express ourselves.
Hope this really-really long reply will let you look at the idea at another angle.
Cheers!
Hey, Brad!
Almost a year after I think I heard you 🙂
I've created an operator-based API!
Check it out: github.com/erql
What do you think?
Wow! Please show how to do the Konami code! [up, up, down, down, left, right, left, right, b, a, enter];..
Might just be me, but I think your solution might compare well against:
gist.github.com/MartinSeeler/f89dd...
Hey, Dean!
Yeah! In the second part I'm exploring an operator based approach, where
exec('DM*U' , { D, M, U })
transforms intoquery(D, some(M), U)
(note thatsome
is an operator example). And with that format we potentially could define our custom operators to match particular keydown events. So, I think, with a few tweaks it could look something like this: