Вопрос или проблема
Я разрабатываю приложение на 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()
. Это предотвращает дальнейшее выполнение оставшихся обработчиков для этого конкретного события на текущем элементе, но не предотвращает его первоначальное вызов на уровне директивы.
Вот несколько решений вашей проблемы, которые могут помочь:
-
Отмена всплытия события:
Вместо использования только
stopImmediatePropagation()
, вы можете также вызватьpreventDefault()
в вашем методеonClick()
. Это не обязательно, но поможет ясно показать, что событие не должно продолжить свою работу:onClick(event: Event) { if (this.isDisabled) { event.stopImmediatePropagation(); event.preventDefault(); // Добавьте это } else { this.click.emit(event); } }
-
Проверка состояния кнопки в директиве:
Вы можете изменить обработчик в вашей директиве, чтобы игнорировать события, если кнопка отключена. Например:
@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); } }
-
Используйте директиву на базовом уровне:
Рассмотрите возможность перемещения вашей директивы на уровень компонента, чтобы она могла управлять состоянием кнопки непосредственно. Таким образом, логика обработки клика будет в самом компоненте, а не в директиве.
Каждое из этих решений может помочь вам добиться желаемого поведения. Первая рекомендация даст вам большую гибкость в контроле событий, а вторая – больше явности в вашей логике обработки событий. Выберите тот подход, который лучше всего подходит для вашего проекта и архитектуры приложения.