Reputation: 3718
I'm trying to prevent click event on disabled buttons, in other words, prevent some user who removes the disabled attribute to call some action.
For now, I have the following code to do this:
<button [disabled]="someCondition" (click)="executeAction()">Execute action</button>
executeAction(): void {
if (this.someCondition) return;
// ...
}
Works, but it isn't a good solution as I have to do it for ALL buttons in my app (and believe me, it's easy to forgot to do this and even a Linter can't help me here).
Looking for a more robust solution, I thought that directive
could help me:
import { Directive, HostListener, Input, Renderer2, ElementRef } from '@angular/core';
@Directive({
selector: 'button'
})
export class ButtonDirective {
@Input() set disabled(value: boolean) {
this._disabled = value != null;
this.renderer2.setAttribute(this.elementRef.nativeElement, 'disabled', `${this._disabled}`);
}
private _disabled: boolean;
constructor(
private readonly elementRef: ElementRef,
private readonly renderer2: Renderer2
) { }
@HostListener('click', ['$event'])
onClick(mouseEvent: MouseEvent) {
// nothing here does what I'm expecting
if (this._disabled) {
mouseEvent.preventDefault();
mouseEvent.stopImmediatePropagation();
mouseEvent.stopPropagation();
return false; // just for test
}
}
}
<button [disabled]="someCondition" (click)="executeAction()">Execute action</button>
executeAction(): void {
console.log('still being called');
}
...however it does absolutely nothing. It doesn't prevent the click
event. Is there any solution that I don't have to control the action itself in its call?
Upvotes: 6
Views: 8230
Reputation: 3373
The Angular 17+ way with signals and the HostBinding
& HostListener
decorators:
import { Directive, ElementRef, input, Renderer2 } from '@angular/core';
@Directive({ selector: 'button' })
export class ButtonDirective {
readonly disabled = input(false);
@HostListener('touchend', ['$event']) onTouchEnd(event: TouchEvent): void {
if (this.disabled()) {
event.preventDefault();
event.stopPropagation();
}
}
@HostBinding('style.pointer-events') get pointerEvents(): string {
return this.disabled() ? 'none' : 'auto';
}
}
As stated in Ahmed's answer, the pointer-events
CSS property being set to none
prevents clicks from being propagated to Angular. However, this is not enough to make it work on mobile (touch) devices as well. For touch, you need to prevent the propagation of touchend
events as well!
Upvotes: 0
Reputation: 31
In our project, the following worked to prevent the click event for a disabled button without having to define your own custom events:
Component template:
<button class="btn"
[disabled]="disabled || inProgress">
<span (click)="disabled ? $event.stopPropagation() : undefined">
<ng-content></ng-content>
</span>
</button>
Works for usage:
<app-button (click)="onClick()">
<span>Click me</span>
</app-button>
Upvotes: 0
Reputation: 229
This is a workaround with CSS which cheaper than scripts.
You easily could use
pointer-events: none;
In this case, the button will not be clickable.
As a UX enhance you could also wrap your button inside a div and give this div a CSS property
cursor: not-allowed;
Which will show the blocked circle icon instead of normal mouse view when hover.
Upvotes: 6
Reputation: 2085
In your directive, you can do something like this. You can achieve it by adding an event listener to parent in the capturing phase.
ngOnInit() {
this.elementRef.nativeElement.parentElement.addEventListener('click',(e) => {
if(this._disabled && e.target.tagName === 'BUTTON') {
e.stopImmediatePropagation();
e.stopPropagation();
}
}, true);
}
You can remove the listener in onDestroy
Upvotes: 2
Reputation: 276141
Prevent click event on disabled buttons
If the disabled
attribute is there the click will not happen.
However if the user edits the HTML and removes the disabled attribute manually, then click will happen. You can try and do the check as you have suggested, but the browser is an unsafe environment. The user will still be able to execute any code on the webpages behalf irrespective of any frontend checks you might put in.
Upvotes: -1