Можете ли вы предотвратить щелчок хоста компонента Angular от обстрела?

Я создаю компонент Angular, который обертывает собственный <button> элемент с некоторыми дополнительными функциями. Кнопки не запускают событие клика, если они отключены, и я хочу реплицировать те же функции. т.е. при условии:

<my-button (click)="onClick()" [isDisabled]="true">Save</my-button>

Есть ли способ my-button предотвратить onClick() от вызова?

В Angular вы можете прослушивать событие щелчка хоста таким образом и прекратить распространение события:

//Inside my-button component
@HostListener('click', ['$event'])
onHostClick(event: MouseEvent) {
  event.stopPropagation();
}

Это предотвращает появление пузырьков в элементах предка, но не останавливает вывод встроенного (click) вывода на один и тот же элемент узла.

Есть ли способ сделать это?


Изменить 1:, как я решаю это сейчас, используя другой вывод, называемый "onClick", и потребители должны знать, использовать "onClick" вместо "click". Это не идеально.

Изменить 2: События события, которые начинаются с элемента <button>, успешно завершены. Но если вы поместите элементы внутри тега кнопки, как я есть, щелкните события по этим целям, распространяйте их до хоста. Hm, должна быть возможность обернуть кнопку в другом элементе, который останавливает распространение...

Ответ 1

Вы можете сделать следующее:

  • Переопределите событие click компонента и сгенерируйте это событие при нажатии кнопки
  • Установите стиль CSS pointer-events: none на хосте компонента
  • Установите стиль CSS pointer-events: auto на кнопке
  • Вызвать event.stopPropagation() в обработчике событий нажатия кнопки

Если вам нужно обработать событие click других элементов внутри вашего компонента, установите для них атрибут стиля pointer-events: auto и вызовите event.stopPropagation() в их обработчике события click.

Вы можете проверить код в этом стеке.

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

@Component({
  selector: 'my-button',
  host: {
    "[style.pointer-events]": "'none'"
  },
  template: '
    <button (click)="onButtonClick($event)" [disabled]="isDisabled" >...</button>
    <span (click)="onSpanClick($event)">Span element</span>',
  styles: ['button, span { pointer-events: auto; }']
})
export class MyCustomComponent {

  @Input() public isDisabled: boolean = false;
  @Output() public click: EventEmitter<MouseEvent> = new EventEmitter();

  onButtonClick(event: MouseEvent) {
    event.stopPropagation();
    this.click.emit(event);
  }

  onSpanClick(event: MouseEvent) {
    event.stopPropagation();
  }
}

UPDATE:

Поскольку кнопка может содержать дочерние элементы HTML (span, img и т.д.), Вы можете добавить следующий стиль CSS, чтобы предотвратить распространение щелчка на родительский элемент:

:host ::ng-deep button * { 
  pointer-events: none; 
}

Спасибо @ErikWitkowski за его комментарий к этому особому случаю. Посмотрите этот стек для демонстрации.

Ответ 2

Я не верю, что есть собственный способ предотвратить запуск события, поддерживаемый этот git вопрос в 2016 году:

Порядок выполнения - это красная селедка - порядок, в котором событие одного и того же элемента распространяется на несколько слушателей, в настоящее время undefined. это в настоящее время по дизайну.

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

Ответ 3

Вы можете использовать собственное дополнение и удалить EventListeners. Это никоим образом не является хорошим решением, если вы думаете в терминах angular. Кроме того, это не сработает, если вы поместите атрибут disabled в button, поскольку он переопределит присоединение eventListeners. Вместо этого следует использовать класс disabled. (Или заверните button в span и используйте шаблон ref #btn из него.)

StackBlitz

import { Component, OnInit, OnChanges, HostListener, Input, Output, EventEmitter, SimpleChanges, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-my-button',
  template: `<button [class.disabled]="isDisabled" #btn><span>hey</span></button>`,
  styles: [`button.disabled { opacity:0.5 }`]
})
export class MyButtonComponent implements OnInit, OnChanges {
  disableClick = e => e.stopPropagation();
  @Input() isDisabled: boolean;
  @ViewChild('btn') btn: ElementRef;
  constructor() { }

  ngOnChanges(changes: SimpleChanges) {
    if(this.isDisabled) {
      this.btn.nativeElement.addEventListener('click', this.disableClick);
    } else {
      this.btn.nativeElement.removeEventListener('click', this.disableClick);
    }
  }
  ngOnInit() {
  }

}