Angular 中自定义 Debounce Click 指令

在这篇文章中,咱们将介绍使用 Angular Directive API 来建立自定义 debounce click 指令。该指令将处理在指定时间内屡次点击事件,这有助于防止重复的操做。html

对于咱们的示例,咱们但愿在产生点击事件时,实现去抖动处理。接下来咱们将介绍 Directive API,HostListener API 和 RxJS 中 debounceTime 操做符的相关知识。首先,咱们须要建立 DebounceClickDirective 指令并将其注册到咱们的 app.module.ts 文件中:typescript

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

@Directive({
  selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit {
  constructor() { }
  ngOnInit() { }
}


@NgModule({
  imports: [BrowserModule],
  declarations: [
    AppComponent,
    DebounceClickDirective
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Angular 指令是没有模板的组件,咱们将使用如下方式应用上面的自定义指令:bootstrap

<button appDebounceClick>Debounced Click</button>

在上面 HTML 代码中的宿主元素是按钮,接下来咱们要作的第一件事就是监听宿主元素的点击事件,所以咱们能够将如下代码添加到咱们的自定义指令中。浏览器

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

@Directive({
  selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit {
  constructor() { }

  ngOnInit() { }

  @HostListener('click', ['$event'])
  clickEvent(event: MouseEvent) {
    event.preventDefault();
    event.stopPropagation();
    console.log('Click from Host Element!');
  }
}

在上面的例子中,咱们使用了 Angular @HostListener 装饰器,该装饰器容许你轻松地监听宿主元素上的事件。在咱们的示例中,第一个参数是事件名。第二个参数 $event,这用于告诉 Angular 将点击事件传递给咱们的 clickEvent() 方法。app

在事件处理函数中,咱们能够调用 event.preventDefault()event.stopPropagation() 方法来阻止浏览器的默认行为和事件冒泡。ide

Debounce Events

如今咱们能够拦截宿主元素的 click 事件,此时咱们还须要有一种方法实现事件的去抖动处理,而后将它从新发送回父节点。这时咱们须要借助事件发射器和 RxJS 中的 debounce 操做符。函数

import { Directive, EventEmitter, HostListener, OnInit, Output } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime';

@Directive({
    selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit {
    @Output() debounceClick = new EventEmitter();
    private clicks = new Subject<any>();

    constructor() { }

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

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

在上面的代码中,咱们使用 Angular @Output 属性装饰器和 EventEmitter 类,它们容许咱们在指令上建立自定义事件。要发出事件,咱们须要调用 EventEmitter 实例上的 emit() 方法。this

但咱们不想当即发出点击事件,咱们想作去抖动处理。为了实现这个功能,咱们将使用 RxJS 中的 Subject 类。在咱们的代码中,咱们建立一个主题来处理咱们的点击事件。在咱们的方法中,咱们调用 next() 方法来让 Subject 对象发出下一个值。此外咱们也使用 RxJS 中 debounceTime 的操做符,这容许咱们经过设置给定的毫秒数来去抖动点击事件。code

一旦咱们设置好了,咱们如今能够在下面的模板中监听咱们的自定义去抖动点击事件。htm

<button appDebounceClick (debounceClick)="log($event)">
  Debounced Click
</button>

如今,当咱们点击咱们的按钮时,它将延迟 500 毫秒。 500毫秒后,咱们的自定义输出属性将会发出点击事件。如今咱们有了基本的功能,咱们须要作一些清理工做,并增长一些其它的功能。

Unsubscribe

对于 RxJS 中 Observables 和 Subject 对象,一旦咱们再也不使用它们,咱们必须取消订阅事件。若是咱们没有执行取消订阅操做,有可能会出现内存泄漏。

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

@Directive({
    selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit, OnDestroy {
    @Output() debounceClick = new EventEmitter();
    private clicks = new Subject<any>();
    private subscription: Subscription;

    constructor() { }

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

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

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

要取消订阅,咱们须要保存订阅时返回的订阅对象。当 Angular 销毁组件时,它将调用 OnDestroy 生命周期钩子,所以咱们能够在这个钩子中,执行取消订阅操做。

Custom Inputs

咱们指令的功能已基本齐全,它能够正常处理事件。接下来,咱们将添加一些更多的逻辑,以便咱们能够自定义去抖动时间。为此,咱们将使用 @Input 装饰器。

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

@Directive({
    selector: '[appDebounceClick]'
})
export class DebounceClickDirective implements OnInit, OnDestroy {
    @Input() debounceTime = 500;
    @Output() debounceClick = new EventEmitter();
    private clicks = new Subject<any>();
    private subscription: Subscription;

    constructor() { }

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

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

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

@Input 装饰器容许咱们将自定义延迟时间传递到咱们的组件或指令中。在上面的代码中,咱们能够经过组件的输入属性,来指定咱们但愿去抖动的时间。默认状况下,咱们将其设置为 500 毫秒。

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

参考资源