Gelso77
Gelso77

Reputation: 1895

Angular component: disable click event

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();
});

Upvotes: 14

Views: 77109

Answers (10)

omostan
omostan

Reputation: 881

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

Upvotes: 1

T. Mad
T. Mad

Reputation: 31

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;
}
}
}

Upvotes: 3

Benedikt Zehetmayer
Benedikt Zehetmayer

Reputation: 121

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>

Upvotes: 7

Jakob Aar&#248;e Dam
Jakob Aar&#248;e Dam

Reputation: 182

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.

Upvotes: 1

ErikWitkowski
ErikWitkowski

Reputation: 470

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.

Upvotes: 17

Sheik Althaf
Sheik Althaf

Reputation: 1605

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>

Upvotes: 14

dm777
dm777

Reputation: 117

You can check it on (click) attribute:

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

Upvotes: 4

Antoine Delia
Antoine Delia

Reputation: 1845

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.

Upvotes: 0

cucuru
cucuru

Reputation: 3698

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>

Upvotes: 0

javapedia.net
javapedia.net

Reputation: 2731

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;
}
......
}

Upvotes: 0

Related Questions