dev_054
dev_054

Reputation: 3718

Angular - Prevent click event on disabled buttons

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?

STACKBLITZ

Upvotes: 6

Views: 8230

Answers (5)

times29
times29

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

Darman
Darman

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

Ahmed Samir Elshazly
Ahmed Samir Elshazly

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

prabhatojha
prabhatojha

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

basarat
basarat

Reputation: 276141

Prevent click event on disabled buttons

If the disabled attribute is there the click will not happen.

When user decides to use devtools

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

Related Questions