Почему event.stopPropagation() не предотвращает получение событий клика в родительском директиве @HostListener в Angular?

Вопрос или проблема

Я разрабатываю приложение на Angular и у меня есть кастомный компонент кнопки, который использует директиву для ограничения событий клика. Даже когда кнопка отключена, нажатие на нее все равно вызывает функцию, связанную с событием клика в родительской директиве.

Вот упрощенная версия моей настройки:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-button',
  template: `
    <button [disabled]="isDisabled" (click)="onClick($event)">
      <ng-content></ng-content>
    </button>
  `,
})
export class ButtonComponent {
  @Input() isDisabled = false;
  @Output() click = new EventEmitter<Event>();

  onClick(event: Event) {
    if (this.isDisabled) {
      event.stopImmediatePropagation()
    } else {
      this.click.emit(event);
    }
  }
}
import { Directive, Output, EventEmitter, HostListener } from '@angular/core';

@Directive({
  selector: '[throttledClick]',
})
export class ThrottledClickDirective {
  @Output() throttledClick = new EventEmitter<Event>();
  private isThrottled = false;

  @HostListener('click', ['$event'])
  handleClick(event: Event) {
    if (!this.isThrottled) {
      this.isThrottled = true;
      this.throttledClick.emit(event);
      setTimeout(() => this.isThrottled = false, 200);
    }
  }
}
<app-button
  throttledClick
  [isDisabled]="isButtonDisabled"
  (throttledClick)="onThrottledClick()">
  Нажми на меня
</app-button>

Когда isButtonDisabled равно true, кнопка отключена, но нажатие на нее все равно вызывает метод onThrottledClick(). Я ожидал, что event.stopPropagation() в компоненте кнопки предотвратит событие клика от достижения директивы, но этого не происходит.

Ответ или решение

Когда вы используете метод event.stopPropagation() в вашем компоненте кнопки, вы предотвращаете дальнейшее распространение события на другие элементы (родительские элементы) в иерархии DOM. Однако в вашем случае, это не предотвращает вызов обработчика событий @HostListener в директиве, которую вы объявили на вашем компоненте кнопки. Это связано с тем, что stopPropagation() останавливает распространение события только вверх по дереву событий, но не влияет на саму логику обработки события на том же уровне или ниже.

Ваша структура кода понятна, и давайте детально рассмотрим, чтобы решить эту проблему. Ваша кнопка вызывает метод onClick(event) и внутри него вы применяете event.stopImmediatePropagation(). Это предотвращает дальнейшее выполнение оставшихся обработчиков для этого конкретного события на текущем элементе, но не предотвращает его первоначальное вызов на уровне директивы.

Вот несколько решений вашей проблемы, которые могут помочь:

  1. Отмена всплытия события:

    Вместо использования только stopImmediatePropagation(), вы можете также вызвать preventDefault() в вашем методе onClick(). Это не обязательно, но поможет ясно показать, что событие не должно продолжить свою работу:

    onClick(event: Event) {
        if (this.isDisabled) {
            event.stopImmediatePropagation();
            event.preventDefault(); // Добавьте это
        } else {
            this.click.emit(event);
        }
    }
  2. Проверка состояния кнопки в директиве:

    Вы можете изменить обработчик в вашей директиве, чтобы игнорировать события, если кнопка отключена. Например:

    @HostListener('click', ['$event'])
    handleClick(event: Event) {
        const button = event.target as HTMLButtonElement;
        if (button.disabled) { // Проверяйте состояние кнопки
            return; // Если кнопка отключена, просто выходим
        }
    
        if (!this.isThrottled) {
            this.isThrottled = true;
            this.throttledClick.emit(event);
            setTimeout(() => this.isThrottled = false, 200);
        }
    }
  3. Используйте директиву на базовом уровне:

    Рассмотрите возможность перемещения вашей директивы на уровень компонента, чтобы она могла управлять состоянием кнопки непосредственно. Таким образом, логика обработки клика будет в самом компоненте, а не в директиве.

Каждое из этих решений может помочь вам добиться желаемого поведения. Первая рекомендация даст вам большую гибкость в контроле событий, а вторая – больше явности в вашей логике обработки событий. Выберите тот подход, который лучше всего подходит для вашего проекта и архитектуры приложения.

Оцените материал
Добавить комментарий

Капча загружается...