14

I am trying to write a Angular2 attribute directive to modify the behaviour of certain elements. More specifically I want to apply an attribute to certain elements that have click handlers and prevent the bound function to be executed under certain conditions.

So now I have an element e.g.:

<button (click)="onClick(param1, param2)"></button>

onClick is a function declared on the component that hosts the button element doing some work.

What I would like to do is write something like:

<button (click)="onClick(param1, param2)" online-only></button>

and have a directive like:

@Directive({
  selector: '[online-only]',
})
export class OnlineOnlyDirective {
  @HostListener('click', ['$event']) 
  onClick(e) {
    if(someCondition){
      e.preventDefault();
      e.stopPropagation();
    }
  }
}

But click handler is executed first, thus not giving my directive the opportunity to stop its execution.

A second approach I thought about was replacing (click) with my own handler e.g.( [onlineClick]="onClick" ) and execute the passed function when the directive thinks fit, but this way I cannot pass params to onClick function and is a bit weirder to look at.

Do you have any thoughts on doing something like that?

masimplo
  • 3,674
  • 2
  • 30
  • 47

3 Answers3

20

I don't know of a way to force Angular to execute a certain event handler first. A workaround might be to use a custom event like:

<button (myClick)="onClick(param1, param2)" online-only></button>
@Directive({
  selector: '[myClick]',
})
export class OnlineOnlyDirective {
  @Output() myClick: EventEmitter = new EventEmitter();
  @HostListener('click', ['$event']) 
  onClick(e) {
    if(someCondition){
      e.preventDefault();
      e.stopPropagation();
    } else {
      this.myClick.next(e);
    }
  }
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Yeap, much better take on my described 2nd approach. Using an EventEmitter instead of passing in the function to call makes all the difference. The first approach would be more elegant, but probably is not supported by angular2 as you said also. – masimplo Jul 20 '16 at 14:22
  • 2
    Just curious if the selector was supposed to be '[online-only]' ? Trying to figure out what happened to that directive. – Will Lanni Dec 20 '17 at 04:24
  • I don't know if or how `online-only` is related to the question – Günter Zöchbauer Dec 20 '17 at 06:20
  • I think the selector is supposed to be `selector: '[online-only]',`. It's the glue that pulls in this directive. – doogie Aug 29 '20 at 07:40
  • I agree with you @WillLanni that does seem confusing, to have the selector named differently as the directive is misleading – vhbazan Jun 13 '23 at 07:49
3

So far this is not possible in a way you wanted to do that (just using (click) binding). This is because all events registered via Angular -> by (click) binding, @HostListner, are proxied via single listener. That's why calling stopPropagation or more correctly in this case stopImmediatePropagation doesn't work, as you don't have separate event listeners any more. Please reference this issue for more details: https://github.com/angular/angular/issues/9587.

wawka
  • 4,828
  • 3
  • 28
  • 22
2

I recently went through something similar that I wanted to do and the suggested answer did not work maybe because I had the click handler on a custom component. This is what I did.

<button (myClick)="onClick(param1, param2)" online-only></button>

Or

<my-cmp (myClick)="onClick(param1, param2)" online-only></my-cmp>

The directive can look like so:

@Directive({
   selector: '[online-only]',
})
export class OnlineOnlyDirective implements OnInit {
   constructor(private el: ElementRef) { }

   ngOnInit() {
      if (someCondition) {
         this.el.nativeElement.removeAllListeners('click');
      }
   }
}

This will remove the click event listener on the component or element. This won't be reactive, so if someCondition changes there has to be a way to put the click listener back. I did not need this use case though.

Update

This worked for me in development but did not work for me when the code got minified in production environment.

AliF50
  • 16,947
  • 1
  • 21
  • 37