Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 Directive to modify click handling

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?

like image 251
masimplo Avatar asked Jul 20 '16 13:07

masimplo


3 Answers

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.

like image 163
wawka Avatar answered Sep 20 '22 00:09

wawka


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);
    }
  }
}
like image 23
Günter Zöchbauer Avatar answered Sep 21 '22 00:09

Günter Zöchbauer


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.

like image 26
AliF50 Avatar answered Sep 21 '22 00:09

AliF50