如何防止在 Angular 中双击?

How to prevent double click in Angular?

我有一个组件click

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

当我单击此元素时,openModal 函数将 运行。 我想给 1000ms 节流时间以防止打开多个模态。

我的第一个方法是使用 Subject(来自 rxJs)

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

但我觉得有点冗长

下一个方法是使用 directive。 我修改了一些通过谷歌搜索找到的代码。

//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>

然而,无论我尝试什么,总是 openModal 被执行。 我找不到如何在指令中停止执行 openModal

我可以点赞

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

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

但对于可重用代码,我认为 using 指令是理想的。

我怎样才能做到?

您可以使用 RxJs 的 debounce or debounceTime operator to prevent double clicks. Here 也是关于如何创建自定义去抖动点击指令的 post。

以防将来post被取下,这里是最终代码:

指令:

import { 
  Directive, 
  EventEmitter, 
  HostListener, 
  Input, 
  OnDestroy, 
  OnInit, 
  Output 
} from '@angular/core';
import { Subject, Subscription } from 'rxjs';
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);
  }
}

用法示例:

<button appDebounceClick (debounceClick)="log()" [debounceTime]="700">Debounced Click</button>

在我的例子中,throttleTime 而不是去抖动是更好的解决方案(立即触发事件并阻止直到一段时间过去)

由于有人要求throttleTime指令,我将其添加在下面。我选择走这条路是因为 debounceTime 在触发实际点击事件之前等待最后一次点击。 throttleTime 将不允许答题器再次单击按钮,直到达到该时间,而是立即触发单击事件。

指令

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);
  }
}

用法示例

throttleTime 是可选的,因为指令中有默认值 500

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

如果您有一个每 1 毫秒点击您的元素的机器人,那么您会注意到该事件只会触发一次,直到 throttleTime 启动。

我提出了一种更简单的按钮方法:

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

    const DISABLE_TIME = 300;
    
    @Directive({
        selector: 'button[n-submit]'
    })
    export class DisableButtonOnSubmitDirective {
        constructor(private elementRef: ElementRef) { }
        @HostListener('click', ['$event'])
        clickEvent() {
            this.elementRef.nativeElement.setAttribute('disabled', 'true');
            setTimeout(() => this.elementRef.nativeElement.removeAttribute('disabled'), DISABLE_TIME);
        }
    }

用法示例:

    <button n-submit (click)="doSomething()"></button>

或者可能想要防止多次点击按钮?我正在使用以下解决方案:

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

@Directive({
    selector: '[disableAfterClick]'
})
export class DisableButtonAfterClickDirective {
    constructor() { }

    @HostListener('click', ['$event'])
    clickEvent(event) {
        event.preventDefault();
        event.stopPropagation();
        event.currentTarget.disabled = true;
    }
}

我不知道它是否是最有效和最优雅的,但它确实有效。

我会使用自定义指令。

将它放在模板中的某处:

<button appSingleClick (singleClick)="log()" [throttleMillis]="1000">click</button>

SingleClickDirective 指令

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

@Directive({
  selector: '[appSingleClick]'
})
export class SingleClickDirective implements OnInit, OnDestroy {
  private subscription: Subscription;

  @Input()
  throttleMillis = 1500;

  @Output()
  singleClick = new EventEmitter();

  constructor(private elementRef: ElementRef) {
  }

  ngOnInit(): void {
    this.subscription = fromEvent(this.elementRef.nativeElement, 'click')
      .pipe(throttleTime(this.throttleMillis))
      .subscribe((v) => {
        this.singleClick.emit(v);
      });
  }

  ngOnDestroy(): void {
    this.subscription?.unsubscribe();
    this.singleClick.unsubscribe();
  }

}

下面的代码可以防止双击。

onClick(event) {
    const button = (event.srcElement.disabled === undefined) ? event.srcElement.parentElement : event.srcElement;
        button.setAttribute('disabled', true);
        setTimeout(function () {
        button.removeAttribute('disabled');
        }, 1000);
    //Your code}

和HTML:

<button class="btn btn-save" (click)="onClick($event)">
                        Prevent Double click
                    </button>