I'm creating an Angular component that wraps a native <button>
element with some additional features. Buttons do not fire a click event if they're disabled and I want to replicate the same functionality. i.e., given:
<my-button (click)="onClick()" [isDisabled]="true">Save</my-button>
Is there a way for my-button
to prevent onClick()
from getting called?
In Angular you can listen to the host click event this way, and stop propagation of the event:
//Inside my-button component
@HostListener('click', ['$event'])
onHostClick(event: MouseEvent) {
event.stopPropagation();
}
This prevents the event from bubbling to ancestor elements, but it does not stop the built-in (click)
output from firing on the same host element.
Is there a way to accomplish this?
Edit 1: the way I'm solving this now is by using a different output called "onClick", and consumers have to know to use "onClick" instead of "click". It's not ideal.
Edit 2: Click events that originate on the <button>
element are successfully stopped. But if you put elements inside the button tag as I have, click events on those targets do propagate up to the host. Hm, it should be possible to wrap the button in another element which stops propagation...
Events are handled in Angular using the following special syntax. Bind the target event name within parentheses on the left of an equal sign, and event handler method or statement on the right. Above, (click) binds the button click event and onShow() statement calls the onShow() method of a component.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With