13

I am creating a reusable component like this one:

<my-button [isDisabled]="isDisabled" (click)="click($event)"> submit </my-button>

I would like to disabled the click event when the property isDisabled is true, I tried something like that but it doesn't work.

packages/component/my-button.component.html

<button  [disabled]="isDisabled" #myButton>
        <ng-content></ng-content>
</button>

packages/component/my-button.component.ts

@ViewChild('uxButton') uxButton: ElementRef;
@Input() isDisabled: boolean = false;

this.myButton.nativeElement.parentNode.removeEventListener('click' , (e) => {
       e.stopPropagation();
});
Gelso77
  • 1,763
  • 6
  • 30
  • 47

10 Answers10

16

It is also solved by the following CSS:

# This prevents host to get a direct click
:host {
  pointer-events: none;
}

# This prevents the span inside the button to "steal" the click from the button
:host /deep/ button span {
  pointer-events: none;
}

# Now only the button can get a click 
# Button is the only smart enough to stop propagation when needed
button {
  pointer-events: auto;
}

And now you don't to pass down the click event manually like in other answers: You have the old (click) event back :D

<my-button [isDisabled]="isDisabled" (click)="click($event)"> submit </my-button>

In your custom component, you just need to pass down the disabled property:

<button  [disabled]="isDisabled" #myButton>
        <ng-content></ng-content>
</button>

Also consider the stackblitz modified from another stackoverflow answer.

ErikWitkowski
  • 460
  • 3
  • 8
  • 2
    Take note that css `pointer-events: none` will disable other mouse events. In turn, things like a tooltip will no longer work as there is no mouse event being fired. – Jacob Roberts Jan 11 '21 at 16:08
14

try like this

<button  [disabled]="isDisabled" (click)="btnClick.emit($event)">
        <ng-content></ng-content>
</button>

@Input() isDisabled: boolean = false;
@Output() btnClick = new EventEmitter();

Use Output and By default the button click event won't work if button is disabled. take advantage of it

<my-button [isDisabled]="isDisabled" (btnClick)="click($event)"> submit </my-button>
Sheik Althaf
  • 1,595
  • 10
  • 16
6

If you want to keep the legacy click event without using output. There is a combined solution based on the previous ones.

my-button.component.html

<button [disabled]="disabled">
  <ng-content></ng-content>
</button>

my-button.component.ts

export class MyButtonComponent {
  @HostBinding('style.pointer-events') get pEvents(): string {
    if (this.disabled) {
      return 'none';
    }
    return 'auto';
  }

  @Input()
  disabled: boolean = false;

 constructor() {}
}

parent component where you will call your component e.g. app.component.html

<my-button [disabled]="isDisabled" (click)="onClickFnc()">
  <span>Save which not trigger click when button is disabled</span>
</my-button>
5

You can check it on (click) attribute:

<my-button [isDisabled]="isDisabled" (click)="!isDisabled && click($event)"> submit </my-button>
dm777
  • 119
  • 8
3

I was able to disable the click event of a disabled button in scss. This is what I added

:host {
button {
&:disabled:active {
      pointer-events: none;
}
}
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
T. Mad
  • 31
  • 1
1

This can be archieved by using a boolean variable to check if some condition is true like so:

<my-button (click)="is_some_condition_true ? null : click($event)"> submit </my-button>

This condition could be for example:

setting the initial value of a variable like editMode, addMode or isDisabled in the component to false and then set this to true when the button is actually disabled.

Ref: how-to-disabled-click-event-or-any-event-if-condition-is-false-true-in-angular

omostan
  • 840
  • 8
  • 9
0

We can addeventlistener/remove based on the need using ElementRef/HostListener however the simple fix would be the below.

click(event) {
if (this.isDisabled) {
return;
}
......
}
javapedia.net
  • 2,531
  • 4
  • 25
  • 50
0

I think the problem is in your code you have:

<my-button [isDisabled]="isDisabled" (click)="click($event)"> submit </my-button>

it should be

<my-button [disabled]="isDisabled" (click)="click($event)"> submit </my-button>
cucuru
  • 3,456
  • 8
  • 40
  • 74
0

You should use the [disabled] property as mentioned in the documentation:

<button [disabled]="isDisabled" (click)="disableButton()">Disable button</button>

And then in your code

export class AppComponent  {
  isDisabled = false;

  disableButton() {
    this.isDisabled = true;
    // your code...
  }
}

Check the StackBlitz for the demo.

Antoine Delia
  • 1,728
  • 5
  • 26
  • 42
0

First of all we need to ask ourselves why we want to create a new type of button component, when we already have a native one. It could be something like:

  • Take advantage of some Angular helpers such as animations
  • Disable the button during execution of some click handler.
  • Progress indication.
  • ...

If the requirement can be solved with a native button (Solution 0), stick with that. Otherwise, go on.

Two important things we need to know before creating a reusable button component:

K1 Only a limited subset of HTML elements can be disabled, https://html.spec.whatwg.org/multipage/semantics-other.html#disabled-elements. This means that a click handler is triggered even if the element is disabled.

K2 In Angular, outside events can't be controlled from the inside inside. https://github.com/angular/angular/issues/12630

Solution 1. Click handler as input

To workaround K2 you could use an @Input callback instead of an event binding. Then you have control of it from the inside, and you even have access to the result of the callback on the inside. It would look like:

<my-button [myClick]="doIt"> or with arguments <my-button [myClick]="doIt.bind(1)">

@HostListener('click') onClick(event) {
  this.myClick();
}

Since, you have complete control over the callback, you can just omit calling it when it's disabled.

A problem that cries for this solution, is a button with progress indication. When you have complete control of the callback, the library button could start / stop animations of a progress bar or even block additional clicks by disabling it while in progress. Compare that to the progress buttons in this module https://github.com/michaeldoye/mat-progress-buttons where you need to start / stop animations for each instance of the button!

Cons: Non-standard looks. Your library users will be like why is that callback an input and not an event binding...

Solution 2. CSS

You could try to workaround K1 with CSS pointer-events:none. It would work on the surface, blocking user mouse triggered click events. However, you can still click pragmatically on the button. myButton.click() still fires when the button is 'disabled'.

Cons: 'Disabled' elements are still clickable. Probably, not good for your library users writing automated tests.

Solution 3. Componentize native button

For disable and events to work as expected, you need to apply the component directly on the HTML button element. In Angular Material it looks like <button mat-button>, https://github.com/angular/components/blob/master/src/material/button/button.ts#L66

And it's quite simple:

@Component({
  selector: 'button[my-button]',
  template: '<ng-content></ng-content>'
})

And using the it:

<button my-button (click)="doIt()" [disabled]="isDisabled">Save</button>

Now the click event is not fired when my-button is disabled.

Cons: Native button must be there, and my-button looks more as if it were a directive than a component.

Conclusion

I would suggest to go with the solution that best fits the requirement, but not the CSS hack one.