62

Is it possible to define a condition in the template based on which a click handler is attached?

For instance, the closest I can get is evaluating a condition at the entry of the click method.

<a class='user' (click)=" isOverflown? menu.toggle($event): '' "></a>

Is there a way in which I can avoid binding to the click event altogether if the flag isOverflown is false?

Also, I dont want to use ng-if on the element and duplicate the template. ie: create one element that has click binding and create another that doesn't, then show/hide them using ng-if

Himanshu Arora
  • 2,368
  • 3
  • 22
  • 33
  • 1
    There is no way to do enable/disable bindings. You can use `@ViewChild('.user') aUser:ElementRef;` and then `this.aUser.nativeElement.addEventListener(...);` or `removeEventListener()` – Günter Zöchbauer Aug 31 '17 at 15:46
  • What are you trying to achieve exactly when you want to avoid binding ? Why is binding problematic to you ? – Pac0 Aug 31 '17 at 15:47
  • I have a component in which the click behavior is needed only if the user supplies a value for one of the `@Input`. So, I knew only these two ways, of which I do not want to use an `ng-if ` due to the large template being duplicate. So, I was wondering if there is a declarative way to not register the handler at all if a condition is not met. Does this explain what was I trying? – Himanshu Arora Aug 31 '17 at 15:50
  • 1
    Just put `if (!isOverflown) {return}` at the beginning of the method, it'll achieve the same result. – John Montgomery Aug 31 '17 at 15:51
  • @GünterZöchbauer I think this might be a good alternative for my case. You might want to post this as an answer. – Himanshu Arora Aug 31 '17 at 15:55
  • @HimanshuArora : It explains correctly what you want, I agree about the ngIf, but it doesn't explain why you want to remove the binding each time the boolean value changes, instead of simple conditionally execute the code in the binded handler. I don't see how you would benefit from that. – Pac0 Aug 31 '17 at 16:04
  • 3
    @Pac0 not speaking of particular case but in general, event listeners are kind of things that you prefer to have less rather than more. you want to attach them as late as possible, de-attach as early as possible and not use if you don't need them. Its not just about final effect handled in listener method, its about having listener method attached itself. So he is looking for a way to not have it unless he needs it. And its a good practice. – dee zg Aug 31 '17 at 16:36
  • @deezg Ok, thank you for the insight on that. – Pac0 Aug 31 '17 at 18:50
  • just replace your else statement with null and you're good to go – Kode Bryant Sep 29 '17 at 19:06

6 Answers6

115

You can just do it like this

<a class='user' (click)="isOverflown && menu.toggle($event)"></a>
internetzer
  • 1,373
  • 3
  • 9
  • 9
  • 3
    This is a great answer and solution – Chris Knight Dec 13 '19 at 23:32
  • simple and great answer – fingers10 Feb 18 '20 at 09:42
  • Does this also not still add the event listener to the element? – Wilt Jun 17 '20 at 11:20
  • Thanks worked for me! @Wilt Nope it will not call the menu.toggle() if the initial condition is falsy. – Zaki Mohammed Oct 07 '20 at 01:33
  • 4
    @ZakiMohammed You are wrong, when you add a condition like that the listener is still added, but it will simply never be executed when the first condition is evaluates to false. So it will not call the method, but an event handler will still be bound. – Wilt Oct 07 '20 at 05:31
  • 1
    Because of what @Wilt says, something like this won't work as expected: `this.onToggle.observers.length > 0`. As the listener is still created,even if it will never call your method. – Daniel Mar 30 '21 at 19:06
18

There is no way to enable/disable bindings.

It's possible to do that imperatively

@ViewChild('.user') aUser:ElementRef; 

clickHandler(event) {
  console.log(event);
}
_clickHandler = this.clickHandler.bind(this);

ngAfterViewInit() {
  this.aUser.nativeElement.addEventListener('click', this._clickHandler); 
}  

to unsubscribe use

this.aUser.nativeElement.removeEventListener('click', this._clickHandler); 

See also Dynamically add event listener

rofrol
  • 14,438
  • 7
  • 79
  • 77
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
17

I would suggest you write a handler that performs the conditional action, this is the simplest way IMHO :

In Component template :

<a class='user' (click)="myClickHandler($event)"></a>

in Component code .ts :

myClickHandler(event): void {
  if (this.isOverflown) {
    this.menu.toggle(event);
  }
}

EDIT after comment : if you really want to avoid binding (I don't understand why, but anyway) you can have a conditional component using *ngIf:

<a class='user' *ngIf="isOverflown" (click)="menu.toggle($event)"></a>
<a class='user' *ngIf="!isOverflown"></a>
Philipp Meissner
  • 5,273
  • 5
  • 34
  • 59
Pac0
  • 21,465
  • 8
  • 65
  • 74
  • yeah, this is as good as what I did in the template. But I was wondering if binding to the method was avoidable. – Himanshu Arora Aug 31 '17 at 15:34
  • in this case, you could use a `*ngIf`. I will edit my answer. – Pac0 Aug 31 '17 at 15:36
  • Lol, I just edited the question, I do not want to duplicate a large element in the template. The example I put is minimalistic, in reality, I have an elaborate template. – Himanshu Arora Aug 31 '17 at 15:38
  • well, I didn't know that before. I don't know any answer to that right now, then . May I ask why you don't want binding ? This seems to be the Angular way to go. The smells like [an XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Pac0 Aug 31 '17 at 15:40
  • so, it seems like these are the only two clean ways. – Himanshu Arora Aug 31 '17 at 15:41
  • I have a component in which the click behavior is needed only if the user supplies a value for one of the `@Input`. So, I knew only these two ways, of which I do not want to use an `ng-if ` due to the large template being duplicate. So, I was wondering if there is a declarative way to not register the handler at all if a condition is not met. – Himanshu Arora Aug 31 '17 at 15:49
12

Had a similar issue, this worked for me:

<p (click)="item.isClickable ? item.onClick() : return;">
 Hello Mom!
</p>
Craig Wayne
  • 4,499
  • 4
  • 35
  • 50
7

Just binding null with the help of ternary operator and adding disabled class resolved my problem.

<a (click)="item.quantity>1 ? decreaseQuantity():null;" [ngClass]="{'disabled': item.quantity<=1}">
Shyam Narayan
  • 1,009
  • 2
  • 14
  • 28
  • Bootstrap `disabled` class prevents click already. no need to use angular to prevent click. Even though this is the least popular answer, this approach is the simplest to solve the problem as long as Bootstrap exists in the project. – Halil Sep 10 '20 at 09:47
4

You need to inject ElementRef and Renderer into your component and use its listen method on the element reference of interest.

https://angular.io/api/core/Renderer

https://angular.io/api/core/Renderer2

this.renderer.listen(this.elementRef.nativeElement, 'click', callbackFunction)

dee zg
  • 13,793
  • 10
  • 42
  • 82