Angular2 Directive to modify click handling
Asked Answered
T

3

15

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?

Toombs answered 20/7, 2016 at 13:53 Comment(0)
C
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);
    }
  }
}
Charlottecharlottenburg answered 20/7, 2016 at 14:4 Comment(5)
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.Toombs
Just curious if the selector was supposed to be '[online-only]' ? Trying to figure out what happened to that directive.Unanswerable
I don't know if or how online-only is related to the questionOxonian
I think the selector is supposed to be selector: '[online-only]',. It's the glue that pulls in this directive.Phototonus
I agree with you @WillLanni that does seem confusing, to have the selector named differently as the directive is misleadingSifuentes
V
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.

Ventricose answered 15/10, 2017 at 8:24 Comment(0)
B
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.

Barneybarnhart answered 11/6, 2019 at 21:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.