Runtime Terror
Runtime Terror

Reputation: 6742

Click event created with RxJs gets emitted more times than native click event

I need to distinguish between short (< 400ms) and long (> 400ms) clicks. I've implemented both events with RxJs, but the short click is emitting way more than it should if the user starts to click fast on the element.

app.component.html

<button #button (click)="onBuiltInClickEvent()">Click</button>
<p>Method was called: {{ calledTimes }}</p>
<p>Native click was called: {{ nativeClick }}</p>

app.component.ts

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit {
  @ViewChild("button")
  public button: ElementRef;

  public calledTimes: number = 0;
  public nativeClick: number = 0;

  private mouseDown$: Observable<MouseEvent>;
  private mouseUp$: Observable<MouseEvent>;
  private click$: Observable<MouseEvent>;

  public ngAfterViewInit(): void {
    this.mouseDown$ = fromEvent(this.button.nativeElement, "mousedown");
    this.mouseUp$ = fromEvent(this.button.nativeElement, "mouseup");

    this.click$ = this.mouseDown$.pipe(
        flatMap(_ => {
            return this.mouseUp$.pipe(timeoutWith(400, EMPTY));
        })
    );

    this.click$.subscribe(_ => this.onClick());
  }

  private onClick(): void {
    this.calledTimes++;
  }

  public onBuiltInClickEvent(): void {
    this.nativeClick++;
  }
}

enter image description here

Working stackblitz.

Upvotes: 1

Views: 580

Answers (1)

Barremian
Barremian

Reputation: 31125

flatMap wouldn't be the suitable map operator in this scenario. You do not wish to flatten (or merge) the inner observable. In this scenario, only one inner subscription should be active at a time. So you could use switchMap operator.

I also see that you aren't handling the error from the timeout or resubscribing to the observable if it times out. You could use RxJS retryWhen operator for that. Try the following

this.click$ = this.mouseDown$.pipe(
  switchMap(_ => this.mouseUp$.pipe(
    timeout(400),
    retryWhen(e => of('timeout error'))
  ))
);

I've modified your Stackblitz

Upvotes: 2

Related Questions