L.M
L.M

Reputation: 31

RxJs: Variable sample size based on count

I'm pretty new to RxJS and I'm trying to wrap my head around some concepts, but it feels like I'm still missing the right mindset.

I've been tinkering about something that feels like it should be simple, but with no success so far. I'm listening to the DOM and wrapping "keydown" and "keyup" events in Observables

const keyDown$ = Observable.fromEvent(window, 'keydown');
const keyUp$ = Observable.fromEvent(window, 'keyUp');

On the other side of this, I have a listener

keyDown$
    .filter(/logic to get the right arrow keycode/)
    .subscribe(downArrow => moveElementToTheRight())

The problem here is that, if you hold the key down, you'll get a value every x milliseconds, so the element is going to move very fast. This is pretty straightforward to correct

keyDown$
    .filter(/logic to get the right arrow keycode/)
    .sample.pipe(interval(200))
    .subscribe(downArrow => moveElementToTheRight())

What I'm trying to achieve is a bit different, in that I'd want the sample size to change as the user keeps holding the key down, in a sort of ease-in fashion. For example, if the user holds the key for three seconds, then:

I've tried to think of the best approach with no success so far. My first idea was to use a map to get the count of keydown

keyDown$
    .filter(/logic to get the right arrow keycode/)
    .map((value, index) => 
        if index < 10 return value every 500ms
        if index between 10 and 20 return value every 200ms
        /etc/

The problem with this is that the index will never return to 0. I then tried to find a way to reinitialise the index based on Subjects and keyUp events, but it somehow feels wrong and makes me feel like I'm not looking in the right direction.

I've added a quick JSBin of where I've gotten at, where I manage to return both the command and the duration of the keydown using a scan. I imagine I could use the switchMap statement after, but it still someone feels ... wrong ?

http://jsbin.com/jahoqocuxa/edit?js,console,output

I'd really appreciate some input from someone more knowledgeable.

Upvotes: 1

Views: 142

Answers (1)

mkulke
mkulke

Reputation: 496

I think the scan approach you took is fine, maybe a bit imperative.

I think you can model the behaviour you want quite well with the window operator. The stream has to be chopped into windows, the separator being a keyUp$ emission. The stream is converted into a higher order Observable (Observable of Observables). So, in a map operator you can process the individual window segments. In the example below, the first 3 emissions are mapped to A, the rest to B. Finally with mergeAll the higher order Observable is flattened into an ordinary stream of A & B emissions.

const keyDown$ = Rx.Observable.fromEvent(window, 'keydown');
const keyUp$ = Rx.Observable.fromEvent(window, 'keyup');

keyDown$
  .window(keyUp$)
  .map(win => win.map((_, idx) => idx > 2 ? 'B' : 'A'))
  .mergeAll();
  .subscribe(console.log);

The console output when pressing a key for 2s, then releasing it and pressing it again should look like this:

"A"
"A"
"A"
"B"
"B"
"B"
"A"
"A"
"A"
"B"
"B"

Upvotes: 1

Related Questions