I\'m creating an Angular component that wraps a native element with some additional features. Buttons do not fire a click event if they\'re disab
You can use the native add and remove EventListeners. This is in no way a good solution when thinking in angular terms. Also, this won't work if you put disabled attribute in button as it will override eventListeners attached. A disabled class need to be used instead. (Or else wrap button in a span and use template ref #btn from it.)
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() {
}
}
I do not believe there is a native way to prevent the event from firing, as supported by this git issue in 2016:
The order of execution is red herring - the order in which an event on the same element is propagated to multiple listeners is currently undefined. this is currently by design.
Your problem is that the event exposed to the listeners is the real DOM event and calling stopImmediatePropagation() on the provided event stops execution of other listeners registered on this element. However since all the the listeners registered via Angular are proxied by just a single dom listener (for performance reasons) calling stopImmediatePropagation on this event has no effect.
You could do the following:
click event of the component, and emit this event when the button is clickedpointer-events: none on the component hostpointer-events: auto on the buttonevent.stopPropagation() on the button click event handlerIf you need to process the click event of other elements inside of your component, set the style attribute pointer-events: auto on them, and call event.stopPropagation() in their click event handler.
You can test the code in this stackblitz.
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:
Since the button can contain HTML child elements (span, img, etc.), you can add the following CSS style to prevent the click from being propagated to the parent:
:host ::ng-deep button * {
pointer-events: none;
}
Thanks to @ErikWitkowski for his comment on this special case. See this stackblitz for a demo.