【问题标题】:How to prevent double click in Angular?如何防止在Angular中双击?
【发布时间】:2018-12-25 16:37:15
【问题描述】:

我有一个带有click 的组件。

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

当我点击这个元素时,openModal 函数将运行。 而且我想给 1000 毫秒的节流时间,以防止打开多个模式。

我的第一种方法是使用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 指令是理想的。

我该怎么做?

【问题讨论】:

    标签: angular rxjs angular-directive


    【解决方案1】:

    您可以使用 RxJs 的 debouncedebounceTime 运算符来防止双击。 Here 也是一篇关于如何创建自定义去抖动点击指令的帖子。

    万一以后帖子被撤了,这里是最终代码:

    指令:

    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>
    

    【讨论】:

    • 它就像一个魅力!我没有考虑指令中的输出。
    • 是否可以使用原始(点击)属性来减少创建新属性的数量?
    • @sam-herrmann event.stopPropagation() 真的需要吗?这将阻止任何父组件处理单击事件。例如监听所有点击事件的全局不活动计时器
    • 我想不出你不能省略 event.stopPropagation() 的原因。但是如果我需要传播事件,我不确定是否会在 this.debounceClick.emit(e) 之后在订阅中分派事件。我想问题是,您是否认为点击被去抖动并因此不会作为“活动”产生影响?如果您认为所有点击都是一项活动,即使它们没有超过 debounceTime 运算符,那么您可能只需删除 event.stopPropagation()。
    • 我知道这有点老了,但是如果我们使用这样的指令,比如在应用程序上设置 100 个按钮,这会对性能产生负面影响吗?我可以看到所有 100 个按钮都有自己的订阅和取消订阅。
    【解决方案2】:

    由于有人要求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 启动。

    【讨论】:

    • 这太棒了。您还可以使用 throttleTime config {leading: false, trailing: true} 扩展指令以仅允许双击。默认为{ leading: true, trailing: false }
    • 什么是“log()”参数?我尝试按事件进行更改,但没有成功。
    • @MiguelNavas throttleClick 是一个事件发射器。发出该事件时,将调用理论上具有log 函数(直接取自上面接受的答案)的组件。 dzone.com/articles/…angular.io/api/core/EventEmitterdocs.angular.lat/guide/inputs-outputs
    【解决方案3】:

    在我的情况下,throttleTime 而不是 debounce 是更好的解决方案(立即触发事件并阻塞直到一段时间过去)

    【讨论】:

    • 如何发布答案中给出的示例代码?
    【解决方案4】:

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

        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>
    

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

        我会使用自定义指令。

        把它放在你的模板中:

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

        【讨论】:

          【解决方案7】:

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

          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>
          

          【讨论】:

            猜你喜欢
            • 2012-06-01
            • 2017-07-19
            • 1970-01-01
            • 2019-08-29
            • 2022-01-21
            • 2012-04-24
            • 2015-12-21
            • 1970-01-01
            • 2018-04-16
            相关资源
            最近更新 更多