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

82,665

Solution 1

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

Solution 2

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)'/>

Solution 3

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 ... }

Solution 4

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

}

Solution 5

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);
          }
        }
Share:
82,665

Related videos on Youtube

GWorking
Author by

GWorking

Updated on August 26, 2021

Comments

  • GWorking
    GWorking over 2 years

    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?

    • Tom Sawyer
      Tom Sawyer over 7 years
      Can you use rxjs to pass observers from parent to child of the related event?
  • GWorking
    GWorking over 7 years
    Then if I want to add a listener to a specific element of a template (of a component), I would need to create a new and separated component for it? Otherwise I will have a listener for the entire template, isn't it?
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    If the element is inside your component you can use one of stackoverflow.com/questions/32693061/… to get an ElementRef of the element and then you can use ElementRef.nativeElement.addEventListener(...), or you can use stackoverflow.com/a/41610950/217408. If it's outside of your component then you can use document.body.querySelector(...).addEventListener(...)
  • GWorking
    GWorking over 7 years
    The second option doesn't work for me due to stackoverflow.com/questions/41695838/… and I am trying to understand now your first link, thanks!
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    The first link only works for elements added by Angular. If you have some JavaScript code or jQuery library, that creates the elements it won't work.
  • GWorking
    GWorking over 7 years
    They are elements created in angular, they are in a template inside a component, and I'd like to attach a listener to all of those elements with a specific className. But I find it difficult to achieve (this should be straightforward so I must be missing something basic here)
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    Angular doesn't provide support for that approach. There is no Angular2 way of adding event listeners by class. If the elements are added by Angular, then DOMContentLoaded shouldn't be relevant. ngAfterViewInit() should do to be sure the elements exist (except they are removed by *ngIf="falsy".
  • GWorking
    GWorking over 7 years
    It doesn't in my case, in ngAfterViewInit() I have part of the template that is still not displayed and I can't access them (so I can't attach listeners there). It's in the link I've pasted above and I don't have yet a clue of what to do
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    If you get the data async from get_keys there is no lifecycle callback that fits your need. You need to call your code from the callback that you pass to .subscribe(response => { your code here}). The DOM will only be created by *ngFor when the data becomes available.
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    get_keys doesn't look async. I don't know then. You could try to inject constructor(private cdRef:ChangeDetectorRef){} and call this.cdRef.detectChanges() before you query for elements in ngAfterViewInit(), to be sure the view was updated.
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    Just found, it's because current_tabs is async. Sorry, it's to complex, I don't have that much time to investigate.
  • GWorking
    GWorking over 7 years
    Yes it is async but with promises. But you mean that the fact it is async makes it disconnect from the Angular control, even though there're promises in theory taking care of that? But at least now it makes sense ...
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    async means that the callback passed to then(...) or subscribe(...) is called eventually, and only when it's called the data is available, and only when the data is available *ngFor="..." will render the items.
  • GWorking
    GWorking over 7 years
    Yes, and I see that the problem is that I execute the adding of the listeners once the data is available in the form of a variable, but then it takes some time for *ngFor to take this data and display it in the template. I would need something that says "now the view is updated, now you can add the listeners", which is something like what you suggested before, but it's like killing a mosquito with a bazooka
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    You can use the this.cdRef.detectChanges() I mentioned above within the callback. When the data is available and detectChanges() is called, the DOM is done rendering.
  • GWorking
    GWorking over 7 years
    Could you give me an example of declaring this.cdRef.detectChanges() with the callback? I've been reading but I don't get how it works. Just by adding this line within a function doesn't make the function to be called whenever there's a change
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    this.cdRef.detectChanges() invokes change detection once. If you just updated a class field in the current method and want the DOM to be updated accordingly before the next code line is executed, you call it and just continue with the other code below this line.
  • GWorking
    GWorking over 7 years
    It HAS worked, but I'm not sure if this is robust. Because I call this.cdr.detectChanges(); just after the data is filled (via Promise) and before I add the listeners, but here I am trusting that the time that Angular needs to update the template is faster than the call to detectChanges, and I have the feeling that it works by casuality. Shouldn't I check with a setInterval until I find out that the DOM is updated to be sure?
  • Günter Zöchbauer
    Günter Zöchbauer over 7 years
    detectChanges() is doing the DOM updates. It's sync, this means when the call returns (and the next line is executed), then Angular is done updating the DOM.
  • GWorking
    GWorking over 7 years
    Ok, great, then it is solved :D Thanks a lot, actually you could post the solution in the other thread. I've posted one saying the solution is here, so feel free to post there an answer and I'll accept it. Thanks a lot again, very appreciated
  • Jose Anibal Rodriguez
    Jose Anibal Rodriguez over 5 years
    Hi. Your answer is a great one. But, does this handle events for childs elements?
  • Googs
    Googs over 5 years
    @JoseAnibalRodriguez this particular use case is for single HTML elements. To achieve this you would need to import this directive into the appropriate modules where you want to use it, and then as per the HTML above, add the directive to every element you want the event to fire. For example if you had nested child elements that you wanted the event to fire, you would need to supply the directive for those elements also. You could then either define different functions if you needed to distinguish which element was clicked, or pass another parameter to it e.g. myFunction($event, 'element1')
  • Jose Anibal Rodriguez
    Jose Anibal Rodriguez over 5 years
    Yes, I know how to use a directive, I just wanted to know if this approach should work on childs elements. But I figured out that this only works on the host. So thanks.
  • GWorking
    GWorking over 4 years
    @Enthu sorry, long time I took the React road, too disconnected from Angular now ...
  • Mayur Patil
    Mayur Patil over 3 years
    HostListener('document:click') does not work properly; if i expose element as angular element and that component is shadow dom. on click it always returns shadowdom element in event (event.target) rather than actual click element. is there any workaround for this
  • mikegross
    mikegross almost 3 years
    Hostlistener by default listens to the event type specified, you do not need to add it again on the specific component......
  • canbax
    canbax over 2 years
    @mikegross what do you mean?
  • canbax
    canbax over 2 years
    It does work with mouseenter event but not with load event. How can I call a component function when a certain div is loaded?
  • Mathias
    Mathias about 2 years
    It should be new EventEmitter<boolean>() instead of new EventEmitter<false>()