Dynamically add event listener

popClingwrap picture popClingwrap · Jan 29, 2016 · Viewed 155.9k times · Source

I am just starting to mess around with Angular 2 and I wonder if anyone can tell me the best way to dynamically add and remove event listeners from elements.

I have a component set up. When a certain element in the template is clicked I want to add a listener for mousemove to another element of the same template. I then want to remove this listener when a third element is clicked.

I kind of got this working just using plain Javascript to grab the elements and then calling the standard addEventListener() but I wondered if there was a more "Angular2.0" way of doing this that I should be looking into.

Answer

Eric Martinez picture Eric Martinez · Jan 29, 2016

Renderer has been deprecated in Angular 4.0.0-rc.1, read the update below

The angular2 way is to use listen or listenGlobal from Renderer

For example, if you want to add a click event to a Component, you have to use Renderer and ElementRef (this gives you as well the option to use ViewChild, or anything that retrieves the nativeElement)

constructor(elementRef: ElementRef, renderer: Renderer) {

    // Listen to click events in the component
    renderer.listen(elementRef.nativeElement, 'click', (event) => {
      // Do something with 'event'
    })
);

You can use listenGlobal that will give you access to document, body, etc.

renderer.listenGlobal('document', 'click', (event) => {
  // Do something with 'event'
});

Note that since beta.2 both listen and listenGlobal return a function to remove the listener (see breaking changes section from changelog for beta.2). This is to avoid memory leaks in big applications (see #6686).

So to remove the listener we added dynamically we must assign listen or listenGlobal to a variable that will hold the function returned, and then we execute it.

// listenFunc will hold the function returned by "renderer.listen"
listenFunc: Function;

// globalListenFunc will hold the function returned by "renderer.listenGlobal"
globalListenFunc: Function;

constructor(elementRef: ElementRef, renderer: Renderer) {
    
    // We cache the function "listen" returns
    this.listenFunc = renderer.listen(elementRef.nativeElement, 'click', (event) => {
        // Do something with 'event'
    });

    // We cache the function "listenGlobal" returns
    this.globalListenFunc = renderer.listenGlobal('document', 'click', (event) => {
        // Do something with 'event'
    });
}

ngOnDestroy() {
    // We execute both functions to remove the respectives listeners

    // Removes "listen" listener
    this.listenFunc();
    
    // Removs "listenGlobal" listener
    this.globalListenFunc();
}

Here's a plnkr with an example working. The example contains the usage of listen and listenGlobal.

Using RendererV2 with Angular 4.0.0-rc.1+ (Renderer2 since 4.0.0-rc.3)

  • 25/02/2017: Renderer has been deprecated, now we should use RendererV2 (see line below). See the commit.

  • 10/03/2017: RendererV2 was renamed to Renderer2. See the breaking changes.

RendererV2 has no more listenGlobal function for global events (document, body, window). It only has a listen function which achieves both functionalities.

For reference, I'm copy & pasting the source code of the DOM Renderer implementation since it may change (yes, it's angular!).

listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):
      () => void {
    if (typeof target === 'string') {
      return <() => void>this.eventManager.addGlobalEventListener(
          target, event, decoratePreventDefault(callback));
    }
    return <() => void>this.eventManager.addEventListener(
               target, event, decoratePreventDefault(callback)) as() => void;
  }

As you can see, now it verifies if we're passing a string (document, body or window), in which case it will use an internal addGlobalEventListener function. In any other case, when we pass an element (nativeElement) it will use a simple addEventListener

To remove the listener it's the same as it was with Renderer in angular 2.x. listen returns a function, then call that function.

Example

// Add listeners
let global = this.renderer.listen('document', 'click', (evt) => {
  console.log('Clicking the document', evt);
})

let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
  console.log('Clicking the button', evt);
});

// Remove listeners
global();
simple();

plnkr with Angular 4.0.0-rc.1 using RendererV2

plnkr with Angular 4.0.0-rc.3 using Renderer2