Створюємо Angular Debounce Click Directive за допомогою RxJS

6 хв. читання

Для чого існує Debounce Click Directive

У цій публікації ми розглянемо API Angular Directive, щоб створити нашу власну Debounce Click Directive. Вона буде відстежувати кліки протягом певного часу, зазвичай директиву використовують для уникнення повторних дій (у нашому варіанті кліків).

Directive API — особливий спосіб додати поведінку до наявних елементів або компонентів DOM. Зараз ми хочемо відмовити у кліку на елемент або затримати клік. Для цього ми розглянемо Directive API, HostListener API та RxJS.

Створюємо Debounce Click Directive

Створюємо debounce-click.directive.ts і реєструємо її в app.module.ts

// debounce-click.directive.ts
import { Directive, OnInit } from '@angular/core';

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

  ngOnInit() {}
}
// app.module.ts  
declarations: [
  AppComponent,
  DebounceClickDirective
],
exports: [
  DebounceClickDirective
],

Директива в Angular — це, по суті, компонент без шаблону. Поведінка визначена класом директиви, яка буде використана до хост-елемента.

Хост-елемент — це наша кнопка HTML. Насамперед ми прослухаємо, коли на нашу кнопку клікнули, тож додаємо її в app.component.html.

<button appDebounceClick>Debounced Click</button>

Тепер ми використаємо декоратор @HostListener, який дозволяє в Angular слухати різні події (точніше, майже усі події) в JavaScript. У нашому прикладі першою подією і параметром буде click. Другий параметр — це event, ним ми повідомляємо Angular, що необхідно передати цю подію нашому методу clickEvent (event).

Далі викликаємо два методи нашої події event.preventDefault (); і event.stopPropagation () ; — щоб запобігти кліку на батьківські компоненти й можливі врапери нашої кнопки.

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

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

  ngOnInit() {}

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

Тепер, коли ми перехоплюємо подію click на нашому HTML-елементі, нам потрібно мати спосіб блокувати цю події, а потім повторно викликати. У цьому нам допоможе Event Emitter і RxJS Subject.

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

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

  constructor() {}

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

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

Як бачите, ми використовуємо Angular Decorator @Output. Він з класом EventEmitter дозволяє створювати власні події на елементах та компонентах DOM. Щоб випускати події, ми викликаємо подію emit в екземплярі класу Event Emitter.

Ми не хочемо одразу передавати подію click, тож необхідно відкласти подію або зупинити її. Для такої поведінки будемо використовувати клас Subject з бібліотеки RxJS. Subject допоможе нам слухати події, а також передавати їх.

Створюємо Subject у нашій директиві, а у методі clickEvent викликаємо .next (), куди й передаємо наш click. Також ми використаємо debounceTime, це оператор RxJS, він дозволить нам відкласти клік на основі заданої кількості мілісекунд для нашого Subject.

Коли ми це все зробимо, то зможемо прослуховувати наш клік (debounce click) у шаблоні, як показано нижче.

<div class="container">
  <div class="controls">
    <button appDebounceClick (debounceClick)="increase()">Increase</button>
    <button appDebounceClick (debounceClick)="decrease()">Decrease</button>
  </div>
  <span>{{count}}</span>
</div>

Як бачите, для більш наочного прикладу я створив дві кнопки та змінну, що дорівнює 0, одна з яких збільшує нашу змінну, а друга зменшує. Тепер в app.component.ts створимо такі методи й змінну.

count = 0;

increase(){
 this.count += 1;
}

decrease(){
 this.count -= 1;
}

Тепер, коли ми натискаємо на одну із кнопок, клік відтерміновується на 400 мілісекунд, після 400 мілісекунд без кліку наша директива передасть подію click. Тепер у нас є весь необхідний функціонал, лишилося виконати останній крок.

У RxJS Observables і Subject ми мусимо відписуватися від події, інакше доведеться весь час відстежувати її. Якщо цього не зробити, ми можемо випадково створити витоки пам'яті.

Щоб скасувати підписку, ми відловлюємо об'єкт підписки, який повертається під час підписки на властивості класу. Коли Angular видалить елемент DOM, відбудеться один метод із життєвих циклів компоненту (life cycle hook) OnDestroy, де ми й відпишемося від нашого Subject методом .unsubscribe()

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 {
  @Output() debounceClick = new EventEmitter();
  private clicks = new Subject();
  private subscription: Subscription;

  constructor() {}

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

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

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

Трішки більше зручності

Наша директива є повністю функціональною і правильно поводиться з подіями. Далі додамо трохи більше логіки, щоб ми могли налаштувати час відкладання кліку, коли це потрібно. Для цього ми скористаємося Angular-декоратором @Input. І завершуємо нашу директиву:

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

Декоратор @Input дозволяє приймати дані в наші компоненти та директиви. У стандартних налаштуваннях ми раніше встановили відкладання на 400 мілісекунд. За допомогою @Input ми можемо встановити це значення у нашому шаблоні, як показано нижче.

<div class="container">
  <div class="controls">
    <button appDebounceClick (debounceClick)="increase()" [debounceTime]="700">Increase</button>
    <button appDebounceClick (debounceClick)="decrease()" [debounceTime]="700">Decrease</button>
  </div>
  <span>{{count}}</span>
</div>
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.2K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація