Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxJS - detect long mousedown

I want to detect when mousedown is being fired for longer than 500ms, if so - do something. My attempt:

const button = document.querySelector('button')
const stream = Rx.Observable.fromEvent(button, 'mousedown')
const mouseUp$ = Rx.Observable.fromEvent(button, 'mouseup')
stream.delay(500).takeUntil(mouseUp$).subscribe(() => console.log(1))

It works but only the first time it runs. Then, the stream is cancelled due to takeUntil operator. How to make it work everytime?

DEMO

like image 720
feerlay Avatar asked Jan 02 '23 02:01

feerlay


2 Answers

Start a TimerObservable for 500ms on every mouseDown$ event. If mouseUp$ get's fired within 500ms unsubscribe from TimerObservable.

const button = document.querySelector('button')
const mouseDown$ = Rx.Observable.fromEvent(button, 'mousedown')
const mouseUp$ = Rx.Observable.fromEvent(button, 'mouseup')

const stream$ = mouseDown$.switchMap(() => Rx.Observable.TimerObservable(500).takeUntil(mouseUp$));

stream$.subscribe(() => console.log('Only Fired after 500ms'))

RxJS >= 6.0.0

import { switchMap, takeUntil } from 'rxjs/operators';
import { timer, fromEvent } from 'rxjs';

const button = document.querySelector('button')
const mouseDown$ = fromEvent(button, 'mousedown')
const mouseUp$ = fromEvent(button, 'mouseup')

const stream$ = mouseDown$.pipe(
  switchMap(() => timer(500).pipe(takeUntil(mouseUp$)))
);

stream$.subscribe(() => console.log('Only Fired after 500ms'))
like image 116
SplitterAlex Avatar answered Jan 04 '23 14:01

SplitterAlex


Example of directive for mouse hold:

@Directive({ selector: "[appMouseHold]" })
export class MouseHoldDirective implements OnInit, OnDestroy {
  @Input() set appMouseHold(tick: string | number) {
    if (typeof tick === 'string') {
      tick = parseInt(tick, 10);
    }
    
    this.tick = tick || 500;
  }
  private tick: number;
  private readonly _stop = new Subject<void>();
  private readonly _start = new Subject<void>();
  private subscription: Subscription;

  @Output() mousehold = new EventEmitter<number>();
  @Output() mouseholdstart = new EventEmitter<void>();
  @Output() mouseholdend = new EventEmitter<void>();

  ngOnInit() {
    this.subscription = this._start
      .pipe(
        tap(() => this.mouseholdstart.emit()),
        switchMap(() =>
          timer(500, this.tick).pipe(
            takeUntil(this._stop.pipe(tap(() => this.mouseholdend.emit())))
          )
        )
      )
      .subscribe((tick) => {
        this.mousehold.emit(tick);
      });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

  @HostListener("mousedown", ["$event"])
  onMouseDown($event) {
    if ($event.button === 0) {
      this._start.next();
    }
  }

  @HostListener("mouseup")
  onMouseUp() {
    this._stop.next();
  }
}

See Stackblitz

For non-angular usage you can simply replace @HostListener handlers with fromEvent() observables

like image 27
Netanel Draiman Avatar answered Jan 04 '23 14:01

Netanel Draiman