14

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

10 Answers 10

17

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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

3 Comments

i want to do it inside the reusable component
I like your idea but it doesn't work , i can't see any @output in your code
sorry i have made a mistake. Updated the answer. btnClick is a output
9

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>

1 Comment

Very helpful answer for when wrapping a button and wanting to "forward" the disable input to the button
5

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

1 Comment

This should be the right answer I think you might need to do &:has(button[disabled]) though? because the way your have it seems like it would disabled the button click not the host
4

You can check it on (click) attribute:

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

1 Comment

This is not a good answer. If you're using your button in a 100 different places then that's a 100 extra checks.
1

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.

Comments

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

Comments

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

1 Comment

i have to do it inside the reusable component, i used ElementRef but doens't work
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>

1 Comment

isDisabled is an input property for the reuseble componet
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.

1 Comment

i want to do it inside the reusable component

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.