GWorking
GWorking

Reputation: 4341

Angular2, HostListener, how can I target an element? can I target based on class?

In Angular2, how can I target an element within the HostListener decorator?

@HostListener('dragstart', ['$event'])
    onDragStart(ev:Event) {
        console.log(ev);
    }

@HostListener('document: dragstart', ['$event'])
    onDragStart(ev:Event) {
        console.log(ev);
    }

@HostListener('myElement: dragstart', ['$event'])
    onDragStart(ev:Event) {
        console.log(ev);
    }

@HostListener('myElement.myClass: dragstart', ['$event'])
    onDragStart(ev:Event) {
        console.log(ev);
    }

The two first work. Any other thing I've tried raises an EXCEPTION: Unsupported event target undefined for event dragstart

So, can I implement it to a targeted element? How?

Upvotes: 54

Views: 118648

Answers (10)

alex351
alex351

Reputation: 1966

Listen on an element:

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

constructor(private renderer: Renderer2) { }

// Get this.myElement with document.getElement... or ElementRef 
ngOnInit() {

  // scroll or any other event
  this.renderer.listen(this.myElement, 'scroll', (event) => {

    // Do something with 'event'
    console.log(this.myElement.scrollTop);

  });

}}

Upvotes: 10

Nikhil Gaikwad
Nikhil Gaikwad

Reputation: 21

Try this (listening on tab key)

@HostListener('document:keydown.tab', ['$event']) onKeydownHandler(event: KeyboardEvent) {

    if ((document as any).getElementById('parentIdOfHtml').contains((event as any).target)) {
      debugger
    }
  }

Upvotes: 0

jorjun
jorjun

Reputation: 134

If button is clicked up to level deep in directive, then toggle class: hide

import { Directive, HostBinding, HostListener } from '@angular/core'


@Directive({
    selector: '[Toggle]',
})
export class ToggleDirective {
    @HostBinding('class.hide') isClosed = true

    @HostListener('click', ['$event.target']) toggleDropdown(el: HTMLElement) {
        if (this.isButton(el)) this.isClosed = !this.isClosed
    }

    isButton(el: HTMLElement, level = 3) {
        for (let btn: HTMLElement | undefined | null = el; level > 0; level--) {
            if (btn?.tagName === 'BUTTON') return true
            btn = btn?.parentElement
        }
        return false
    }
    constructor() {}
}

Upvotes: 0

Diana Tereshko
Diana Tereshko

Reputation: 147

in component template

<div (event)="onMouseEnter()">
    <p>United States of America (mouseenter)</p>
</div>

<div (event)="onMouseOut()">
    <p>United States of America (mouseout)</p>
</div>

in class component

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

@Component({
  selector: 'app-simpleevent',
  templateUrl: './simpleevent.component.html',
  styleUrls: ['./simpleevent.component.css']
})
export class SimpleeventComponent implements OnInit {
  @HostListener("mouseenter", ["$event"]) onMouseEnter(event: Event) {
    console.log(event.type);
  }

  @HostListener("mouseout", ["$event"]) onMouseOut(event: Event) {
    console.log(event.type);
    console.log(event.target)
  }
  constructor() { }

  ngOnInit() {
  }
}

Upvotes: 1

Katana24
Katana24

Reputation: 8959

As pointed out by Googs, the accepted answer does answer the question but doesn't help find a solution.

I built upon alex's answer as I needed a way to use the functionality I already had in my @HostListener for retrieving a list of notifications for different screen sizes.

For example, in my app - the notifications page has its own route on mobile but existed in the sidebar on tablet and screen sizes above, so I couldn't use the @HostListener there as it would only trigger when I hit the bottom of the whole page and not the sidebar.

Instead I looked up the <div> I was interested in and attached what I needed. So the following code:

attachListenerToContainer() {
    let elementToListenTo = this.ele ? this.ele : 'window';

    this.renderer.listen(elementToListenTo, 'scroll', (event) => {
      if(!this.reachedBottom) {
        if((getHeight(this.ele) + getScrollTop(this.ele)) >= getOffset(this.ele)) {
          this.reachedBottom = true;
          this.getNextNotificationsPage();
        }
      }
    });

    function getScrollTop(ele) {
      return ele ? ele.scrollTop : window.scrollY;
    }
    function getHeight(ele) {
      return ele ? ele.clientHeight : window.innerHeight;
    }
    function getOffset(ele) {
      return ele ? ele.scrollHeight : document.body.offsetHeight;
    }
  }

The this.ele is the container div I'm interested in which I look up in the ngAfterViewInit() lifecycle hook for tablet and above. If I can't find that element then I use the window instead - effectively emulating the @HostListener

Also - here's how I was looking up, in my case, the container element I wanted:

this.ele = document.getElementsByClassName('notifications')[0]; 

Upvotes: 0

Rey
Rey

Reputation: 89

I think better way for global listener is @hostliterner but if you want to target some element you can do like this

<div (event)="onEvent($e)"></div>

in your angular component

onEvent($e) { //do something ... }

Upvotes: 7

Noufal Wayn
Noufal Wayn

Reputation: 114

import { Directive, ElementRef, OnInit, Output, EventEmitter} from '@angular/core';

    @Directive({
          selector: '[checkClick]'
        })

        export class checkClickDirective implements OnInit {

        @Output() public checkClick = new EventEmitter();

        constructor(private _el: ElementRef) { }

        @HostListener('click', ['$event.target']) public onClick(targetElement) {

            const checkClick = this._el.nativeElement.contains(targetElement);

            (checkClick)?this.checkClick.emit(true):this.checkClick.emit(false);
          }
        }

Upvotes: 1

Sahidul Islam
Sahidul Islam

Reputation: 489

You can also target any element without @hostlistener and can add necessary events by the following way

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);
}

Upvotes: -2

Googs
Googs

Reputation: 821

Since the accepted answer doesn't actually help to solve the problem, here is a solution.

A better way to achieve this is by creating a directive, this way you can add the directive to any element you wish, and the listeners will only trigger for this particular element.

For example:

@Directive({
   selector: "[focus-out-directive]"
})
export class FocusOutDirective {
   @Output() onFocusOut: EventEmitter<boolean> = new EventEmitter<false>();

   @HostListener("focusout", ["$event"])
   public onListenerTriggered(event: any): void {
       this.onFocusOut.emit(true);
   }
}

Then on your HTML elements you wish to apply this listener to, just add the directive selector, in this case focus-out-directive, and then supply the function you wish to trigger on your component.

Example:

<input type='text' focus-out-directive (onFocusOut)='myFunction($event)'/>

Upvotes: 47

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657751

@HostListener() only supports window, document, and body as global event targets, otherwise it only supports the components host element.

Upvotes: 72

Related Questions