Making A Typing Timer In Rxjs; Tracking Time Spent Typing
Solution 1:
If you want to continuously update the UI, I don't think there's any way around using a timer - I might have written the stream a little differently by initiating the timer by change-events - but your current stream seems also okay as it is already:
const inputEvents$ = Rx.Observable
.fromEvent($('#input'), 'input');
const typing$ = Rx.Observable.merge(
inputEvents$.mapTo('TYPING'),
inputEvents$.debounceTime(1000).mapTo('IDLE')
)
.distinctUntilChanged()
.do(e => e === 'TYPING' ? showTyping() : showIdle())
.publishReplay(1)
.refCount();
const isTyping$ = typing$
.map(e => e === "TYPING");
const timer$ = isTyping$
.switchMap(isTyping => isTyping ? Rx.Observable.interval(100) : Rx.Observable.never())
.scan(totalMs => (totalMs + 100), 0)
.subscribe(updateTimer);
Live here.
If you don't need to update the UI and just want to capture the duration of the typing, you could use start- and stop-events and map them to timestamps like this e.g.:
const isTyping$ = typing$
.map(e => e === "TYPING");
const exactTimer$ = isTyping$
.map(() => +newDate())
.bufferCount(2)
.map((times) => times[1] - times[0])
.do(updateTimer)
.do(typedTime =>console.log("User typed " + typedTime + "ms"))
.subscribe();
Live here.
Solution 2:
I notice a few problems with your code. The gist of it is good, but if you use different operators you can do the same thing even easier.
First you use switchMap
, this is a nice operator to start a new stream every time a new input arrives. However, what you really want is to continue the current timer as long as the user is typing. A better operator here would be exhaustMap
because exhaustMap
will keep the already active timer until it stops. We can then stop the active timer if the user is not typing for 1 second. That is easily done with .takeUntil(input.debounceTime(1000))
. That would result in the very short query:
input.exhaustMap(() => Rx.Observable.timer(1000).takeUntil(input.debounceTime(1000)))
To this query, we can hook the display events you want, showTyping
, showIdle
etc. We also need to fix the timers index
, as it will reset every time the user stops typing. This can be done with using the second parameter of project function in map
, as this is the index of the value in the current stream.
Rx.Observable.fromEvent($('#input'), 'input')
.publish(input => input
.exhaustMap(() => {
showTyping();
returnRx.Observable.interval(1000)
.takeUntil(input.startWith(0).debounceTime(1001))
.finally(showIdle);
})
).map((_, index) => index + 1) // zero based index, so we add one.
.subscribe(updateTimer);
Notice i used publish
here, but it is not strictly needed as the source is hot. However recommended because we use input
twice and now we do not have to think about if it's hot or cold.
/*** Helper Functions ***/constshowTyping = () =>
$('.typing').text('User is typing...');
constshowIdle = () =>
$('.typing').text('');
constupdateTimer = (x) =>
$('.timer').text(x);
/*** Program Logic ***/Rx.Observable.fromEvent($('#input'), 'input')
.publish(input => input
.exhaustMap(() => {
showTyping();
returnRx.Observable.interval(1000)
.takeUntil(input.startWith(0).debounceTime(1001))
.finally(showIdle);
})
).map((_, index) => index + 1) // zero based index, so we add one.
.subscribe(updateTimer);
<head><scriptsrc="https://code.jquery.com/jquery-3.1.0.js"></script><scriptsrc="https://unpkg.com/@reactivex/rxjs@5.0.0-beta.12/dist/global/Rx.js"></script></head><body><div><div>Seconds spent typing: <spanclass="timer">0</span></div><inputtype="text"id="input"><divclass="typing"></div></div></body>
Post a Comment for "Making A Typing Timer In Rxjs; Tracking Time Spent Typing"