How to prevent double click in Angular?

2020-02-26 09:23发布

I have a component with click.

<my-box (click)="openModal()"></my-box>

When I click this element, openModal function will run. And I'd like to give 1000ms throttle time in order to prevent opening multiple modals.

My first approach was using Subject (from rxJs)

//html
<my-box (click)="someSubject$.next()"></my-box>
//ts
public someSubject$:Subject<any> = new Subject();
...etc subscribe

But I feel it's a bit verbose.

Next Approach was using a directive. I modified a bit of code that I found by googling.

//ts
import {Directive, HostListener} from '@angular/core';

@Directive({
    selector: '[noDoubleClick]'
})
export class PreventDoubleClickDirective {

    constructor() {
    }

    @HostListener('click', ['$event'])
    clickEvent(event) {
        event.stopPropagation();    // not working as I expected.
        event.preventDefault();     // not working as I expected.

        event.srcElement.setAttribute('disabled', true);    // it won't be working unless the element is input.
        event.srcElement.setAttribute('style', 'pointer-events: none;');   // test if 'pointer-events: none' is working but seems not. 

        setTimeout(function () {
            event.srcElement.removeAttribute('disabled');
        }, 500);
    }
}

//html
<my-box noDoubleClick (click)="openModal()"></my-box>

However, whatever I try, always openModal was executed. I couldn't find how to stop executing openModal in the directive.

I can just make like

//ts
//In the openModal method.
openModal() {
    public isClickable = true

    setTimeout(() => {
        this.newsClickable = true;
    }, 1000);
    ...
}

But for the reusable code, I think using directive is ideal.

How can I make it?

3条回答
手持菜刀,她持情操
2楼-- · 2020-02-26 09:26

Since some people asked for the throttleTime directive, I'll add it below. I chose to go this route because the debounceTime waits for the last click before firing the actual click event. throttleTime will not allow the clicker to click the button again until that time is reached and instead fires the click event immediately.

Directive

import { Directive, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { throttleTime } from 'rxjs/operators';

@Directive({
  selector: '[appPreventDoubleClick]'
})
export class PreventDoubleClickDirective implements OnInit, OnDestroy {
  @Input()
  throttleTime = 500;

  @Output()
  throttledClick = new EventEmitter();

  private clicks = new Subject();
  private subscription: Subscription;

  constructor() { }

  ngOnInit() {
    this.subscription = this.clicks.pipe(
      throttleTime(this.throttleTime)
    ).subscribe(e => this.emitThrottledClick(e));
  }

  emitThrottledClick(e) {
    this.throttledClick.emit(e);
  }

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

  @HostListener('click', ['$event'])
  clickEvent(event) {
    event.preventDefault();
    event.stopPropagation();
    this.clicks.next(event);
  }
}

Example Usage

throttleTime is optional since there is a default of 500 in the directive

<button appPreventDoubleClick (throttledClick)="log()" [throttleTime]="700">Throttled Click</button>

If you have a bot that's clicking on your element every 1ms, then you'll notice that the event only ever fires once until the throttleTime is up.

查看更多
贼婆χ
3楼-- · 2020-02-26 09:46

You can use RxJs' debounce or debounceTime operator to prevent double clicks. Here is also a post on how to create a custom debounce click directive.

In case the post is taken down in the future, here is the final code:

Directive:

import { Directive, EventEmitter, HostListener, Input, OnDestroy, OnInit, 
Output } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import { debounceTime } from 'rxjs/operators';

@Directive({
  selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit, OnDestroy {
  @Input() 
  debounceTime = 500;

  @Output() 
  debounceClick = new EventEmitter();

  private clicks = new Subject();
  private subscription: Subscription;

  constructor() { }

  ngOnInit() {
    this.subscription = this.clicks.pipe(
      debounceTime(this.debounceTime)
    ).subscribe(e => this.debounceClick.emit(e));
  }

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

  @HostListener('click', ['$event'])
  clickEvent(event) {
    event.preventDefault();
    event.stopPropagation();
    this.clicks.next(event);
  }
}

Example Usage:

<button appDebounceClick (debounceClick)="log()" [debounceTime]="700">Debounced Click</button>
查看更多
Animai°情兽
4楼-- · 2020-02-26 09:46

In my case throttleTime instead of debounce was better solution(fire event immediately and block until some time passed)

查看更多
登录 后发表回答