73

How can I bind an event listener in rendered elements in Angular 2?

I am using Dragula drag and drop library. It creates dynamic HTML but my event is not bound to dynamic HTML elements.

HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
Sudhanshu sharma
  • 1,917
  • 3
  • 20
  • 29

8 Answers8

144
import { AfterViewInit, Component, ElementRef} from '@angular/core';

constructor(private elementRef:ElementRef) {}

ngAfterViewInit() {
  this.elementRef.nativeElement.querySelector('my-element')
                                .addEventListener('click', this.onClick.bind(this));
}

onClick(event) {
  console.log(event);
}
Al-Mothafar
  • 7,949
  • 7
  • 68
  • 102
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 11
    Any manual cleaning needed (`.removeEventListener()`) after destroying the component or will Angular take care of this? – kraftwer1 Feb 03 '18 at 17:08
  • 4
    No, if you register it imperatively, you'll need to remove it imperatively. – Günter Zöchbauer Feb 03 '18 at 17:08
  • Can we pass any arguments along with `bind` ? – Kannan T Sep 16 '18 at 12:58
  • 2
    @Kannan What you pass to `bind` will be available as `this` inside `onClick() { ... }` when it is called, so yes, basically any object can be passed. – Günter Zöchbauer Sep 19 '18 at 12:46
  • Why do we need to inject an ElementRef into the constructor? Shouldnt a new variable be able to do that? – Henry Jun 05 '20 at 02:41
  • A new variable is only a place to store a value and does nothing by itself. Angular checks the constructor parameters of classes it creates instances of (everything with `@Component...` `@Injectable`... or any other of its decorators) and finds matching values in its injector dictionary which it then passes to the constructor. – Günter Zöchbauer Jun 05 '20 at 03:09
  • 4
    It's a small point but likely to catch some out. The selector will of course need a # or a . at the start depending on if it is an id or a class e.g. `'#my-element'` if `
    `
    – Mike Poole Jun 11 '20 at 14:01
  • 4
    Remember to add `implements AfterViewInit` to the class declaration – tblev Aug 11 '20 at 20:58
  • I can't seem to be able to remove event listeners. Can someone share a sample of how one can do that. Currently I have this: `this.dropArea.addEventListener('dragleave', this.unHighlight.bind(this), false)` & to remove `this.dropArea.removeEventListener('dragleave', this.unHighlight.bind(this), false)` – Junaid Jun 25 '21 at 10:25
  • I'd suggest you assign `this.unHighlight.bind(this), false)` to a variable and use the variable to add and remove, this way you ensure you add and remove the same instance, otherwise `bind()` probably returns a different (new) instance and `remove()` thinks you try to remove something that was never added. – Günter Zöchbauer Jun 25 '21 at 18:27
  • why are we getting 2 events on single click? one with pointerId as 1 and other as -1 – Phaneendra Charyulu Kanduri Jan 18 '22 at 12:56
  • @PhaneendraCharyuluKanduri it's unclear what's going on. Can you demonstrate in a StackBlitz? – Günter Zöchbauer Jan 18 '22 at 15:15
  • 1
    @MikePoole I've almost gave up on managing to listen to element until I saw your comment! it is important enough to write it as an answer instead of a comment. cheers – EranKT May 25 '22 at 21:57
  • Very glad it helped you @EranKT – Mike Poole May 26 '22 at 07:25
45

In order to add an EventListener to an element in angular 2+, we can use the method listen of the Renderer2 service (Renderer is deprecated, so use Renderer2):

listen(target: 'window'|'document'|'body'|any, eventName: string, callback: (event: any) => boolean | void): () => void

Example:

export class ListenDemo implements AfterViewInit { 
   @ViewChild('testElement') 
   private testElement: ElementRef;
   globalInstance: any;       

   constructor(private renderer: Renderer2) {
   }

   ngAfterViewInit() {
       this.globalInstance = this.renderer.listen(this.testElement.nativeElement, 'click', () => {
           this.renderer.setStyle(this.testElement.nativeElement, 'color', 'green');
       });
    }
}

Note:

When you use this method to add an event listener to an element in the dom, you should remove this event listener when the component is destroyed

You can do that this way:

ngOnDestroy() {
  this.globalInstance();
}

The way of use of ElementRef in this method should not expose your angular application to a security risk. for more on this referrer to ElementRef security risk angular 2

HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
  • 4
    Is there need to 'un-listen' onDestroy ? Or does Angular take care of it ? – Wolf359 Apr 02 '18 at 09:18
  • 4
    @MehmetGunacti: Good question. The answer is **YES**, and you should un-listen when ngOnDestroy is called. I'll update the answer with this. just take a reference to ``this.renderer.listen`` like ``globalInstance=this.renderer.listen ...`` and call it after that: ``ngOnDestroy() {this.globalInstance();}`` – HDJEMAI Apr 05 '18 at 00:54
  • @HDJEMAI I really like this answer, but how do we add `#testElement` to the rendered element that's outside our control? – stack247 Apr 12 '19 at 04:24
20

HostListener should be the proper way to bind event into your component:

@Component({
  selector: 'your-element'
})

export class YourElement {
  @HostListener('click', ['$event']) onClick(event) {
     console.log('component is clicked');
     console.log(event);
  }
}
Hans Tiono
  • 808
  • 8
  • 8
8

If you want to bind an event like 'click' for all the elements having same class in the rendered DOM element then you can set up an event listener by using following parts of the code in components.ts file.

import { Component, OnInit, Renderer, ElementRef} from '@angular/core';

constructor( elementRef: ElementRef, renderer: Renderer) {
    dragulaService.drop.subscribe((value) => {
      this.onDrop(value.slice(1));
    });
}

public onDrop(args) {

  let [e, el] = args;

  this.toggleClassComTitle(e,'checked');

}


public toggleClassComTitle(el: any, name: string) {

    el.querySelectorAll('.com-item-title-anchor').forEach( function ( item ) {

      item.addEventListener('click', function(event) {
              console.log("item-clicked");

       });
    });

}
Sagar Arora
  • 1,743
  • 1
  • 10
  • 19
6
@HostListener('window:click', ['$event']) onClick(event){ }

check this below link to detect CapsLock on click, keyup and keydown on current window. No need to add any event in html doc

Detect and warn users about caps lock is on

Gopala Raja Naika
  • 2,321
  • 23
  • 18
0
import { Component, OnInit } from '@angular/core';

create variable inside the component:

onlineStatus: any; 

inside the ngOnInit() lifecycle method, you can write what you like and it will be treated like a normal JS canvas

  ngOnInit(): void {
    const updateNetworkStatus = () => {
      const text = window.navigator.onLine ? ' online' : ' offline'
      this.onlineStatus = text
    }
    
    updateNetworkStatus()
    window.addEventListener('offline', updateNetworkStatus)
    window.addEventListener('online', updateNetworkStatus)
  }

I made a video on how to do this on my YouTube channel.

Ian Campbell
  • 23,484
  • 14
  • 36
  • 57
fruitloaf
  • 1,628
  • 15
  • 10
0

There is a nice way to detect when a child element in Angular is rendered and access it. I found this on Stack Overflow but don't remember where.

  private myElement: ElementRef;
  @ViewChild('mySelector', {static : false}) set content(content: ElementRef) {
    if(content) { // initially setter gets called with undefined
      // debugger;
      this.myElement = content;
    }
  }


<div #mySelector *ngIf="initiallyFalseThenAfterDbResponseIsTrue"></div>
Marian07
  • 2,303
  • 4
  • 27
  • 48
0

This example will help you addEventListener and touch-related swipes gestures.

import { Component, HostListener, Input, OnInit } from '@angular/core';
  
export class AppbarComponent implements OnInit {
  
    touchstartX:number = 0;
    touchstartY:number = 0;
    touchendX:number = 0;
    touchendY:number = 0;

    @HostListener('touchstart', ['$event']) gesuredZonestart(event:any) {
      this.touchstartX = event.changedTouches[0].screenX;
      this.touchstartY = event.changedTouches[0].screenY;  
    }
    
    @HostListener('touchend', ['$event']) gesuredZoneend(event:any) {
      this.touchendX = event.changedTouches[0].screenX;
      this.touchendY = event.changedTouches[0].screenY; 
      this.handleGesure(); 
    }

    handleGesure() {
      var swiped = 'swiped: ';
      if (this.touchendX < this.touchstartX) {
          console.log(swiped + 'left!');
      }
      if (this.touchendX > this.touchstartX) {
          console.log(swiped + 'right!');
      }
      if (this.touchendY < this.touchstartY) {
          console.log(swiped + 'down!'); 
      }
      if (this.touchendY > this.touchstartY) {
          console.log(swiped + 'top!');
      }
      if (this.touchendY == this.touchstartY) {
          console.log('tap!');
      }
    }
}
KATHEESKUMAR
  • 147
  • 1
  • 9