37

I am implementing dragging functionality into an angular app: http://jsfiddle.net/Jjgmz/1/

Part of it, is to listen for mousemove event on a document object.

$(document).mousemove(function(e) {});

How can I listen for document object inside of a component?

Lets pretend this is the contents of component.html file:

<div id="box"></div>

I am using Angular 4.

P.S.
  • 15,970
  • 14
  • 62
  • 86
sanjihan
  • 5,592
  • 11
  • 54
  • 119

1 Answers1

135

1) Using @HostListener (docs). This is the prefered method which should be enough most of the time.

import {Component, NgModule, HostListener} from '@angular/core'

@Component({
  ...
})
export class MyComponent {
  @HostListener('document:mousemove', ['$event']) 
  onMouseMove(e) {
    console.log(e);
  }
  ..
}

2) Similar to the above, you can also use (document:event)="handler" on any DOM element, but the above solution is prefered because the code is cleaner. By using this, it's not immediately obvious from the class that you have a global event listener, and you might add additional ones unnecessarily.

@Component({
  selector: 'my-app',
  template: `
    <div (document:mousemove)="onMouseMove($event)" id="box"></div>
  `,
})
export class MyComponent {
  onMouseMove(e) {
    console.log(e);
  }
}

3) The Renderer (docs) is a lower-level solution; useful when you do not want to clutter your code with methods, but deal with the even elsewhere, for example in a hook, as the following snippet shows. Be careful, though, as you still need to remove the event listener to prevent memory leaks; either do this when you know you won't need it anymore, or in the OnDestroy hook.

import { Component, Renderer2 } from '@angular/core';

@Component({
  ...
})
export class MyComponent {
  globalListenFunc: Function;

  constructor(private renderer: Renderer2) {}

  ngOnInit() {
    this.globalListenFunc = this.renderer.listen('document', 'mousemove', e => {
      console.log(e);
    });
  }

  ngOnDestroy() {
    // remove listener
    this.globalListenFunc();
  }
}

4) Alternative of the first example is a host property, which is discouraged by the Angular Style Guide, since you now keep track of the function name in two places which can be very far apart physically in code. Prefer the first version whenever possible.

@Component({
  ...
  host: {
    '(document:mousemove)': 'onMouseMove($event)'
  }
})
export class MyComponent {
  onMouseMove(e) {
    console.log(e);
  }
}

5) A reactive way, which Angular embraces, is using Observable.fromEvent. This gives you a benefit of all RxJS operators for transforming the stream before executing a function. Be careful, though, as you need to unsubscribe manually to avoid memory leaks (unless you subscribe directly in the template using async pipe; this will handle unsubscription for you). Also don't forget to add-import the operator, as shown in the following snippet.

import { Subscription, fromEvent } from 'rxjs';

@Component({
  ...
})
export class MyComponent {
  subscription: Subscription;

  ngOnInit() {
    this.subscription = 
         fromEvent(document, 'mousemove')
                           .subscribe(e => {
                             console.log(e);
                           });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

6) Of course, document.addEventListener is always a solution as well; but this is not an intended usage in Angular apps; you should choose different ways. Directly accessing DOM is discouraged as it breaks Angular's neat encapsulation of DOM manipulation. Also you could run into problems with Universal and SSR.

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 17
    You should write a book. – sanjihan Sep 24 '17 at 14:06
  • you two should write a book :D – sanjihan Sep 24 '17 at 14:24
  • Thanks for adding all the possibilities. – davidenke May 15 '18 at 22:48
  • in case anyone is wondering you can listen to custom events as well, I was looking for something like this on a different thread: https://stackoverflow.com/questions/50753167/is-it-possible-for-external-files-to-talk-to-angular-5-modules-or-components – Helmut Granda Jun 08 '18 at 14:22
  • 1
    Really great explanation. Just curious, what are my options if I want to unsubscribe from HostListener? I'd like to use the preferred angular method but it seems without an option to unsubscribe, a memory leak could happen and I don't see any information about it being taken care of automatically – Crhistian Ramirez Aug 11 '18 at 03:14
  • 2
    Be careful with solution 1 as it will trigger the changeDetector of your whole application everytime the event is captured, not what your want in my opinion. – Aphax Sep 28 '18 at 14:43
  • In the last solution, when using RxJS 6, you need to omit the Observable and just use fromEvent(document, 'mousemove')..... – GaryP Oct 12 '18 at 13:51
  • 2
    how to listen to this event on the specific element (not on document)? – albanx Nov 11 '18 at 13:03
  • for 5) solution I would precised the type of event returned by using the generic of rxjs operator : fromEvent(document, 'mousemove') – tuxy42 Jan 23 '20 at 21:35
  • 1
    @albanx change `@HostListener('document:mousemove', ['$event'])` to `@HostListener('mousemove', ['$event'])` – paul Jan 29 '20 at 14:17
  • 1
    @paul your solution add an event listener to the whole component, not to an specific element. – Leonardo Dias Feb 20 '20 at 13:17
  • The second solution is BRILLIANT. Thank you, so simple and effective! – Fery Kaszoni Feb 21 '20 at 18:48