Angular2, HostListener, how can I target an element? can I target based on class?
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);
}
}
Related videos on Youtube
GWorking
Updated on August 26, 2021Comments
-
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 over 7 yearsCan you use rxjs to pass observers from parent to child of the related event?
-
-
GWorking over 7 yearsThen 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 over 7 yearsIf 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 useElementRef.nativeElement.addEventListener(...)
, or you can use stackoverflow.com/a/41610950/217408. If it's outside of your component then you can usedocument.body.querySelector(...).addEventListener(...)
-
GWorking over 7 yearsThe 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 over 7 yearsThe 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 over 7 yearsThey 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 over 7 yearsAngular 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 over 7 yearsIt 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 over 7 yearsIf 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 over 7 years
get_keys
doesn't look async. I don't know then. You could try to injectconstructor(private cdRef:ChangeDetectorRef){}
and callthis.cdRef.detectChanges()
before you query for elements inngAfterViewInit()
, to be sure the view was updated. -
Günter Zöchbauer over 7 yearsJust found, it's because
current_tabs
is async. Sorry, it's to complex, I don't have that much time to investigate. -
GWorking over 7 yearsYes 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 over 7 years
async
means that the callback passed tothen(...)
orsubscribe(...)
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 over 7 yearsYes, 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 over 7 yearsYou can use the
this.cdRef.detectChanges()
I mentioned above within the callback. When the data is available anddetectChanges()
is called, the DOM is done rendering. -
GWorking over 7 yearsCould 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 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 over 7 yearsIt 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 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 over 7 yearsOk, 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 over 5 yearsHi. Your answer is a great one. But, does this handle events for childs elements?
-
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 over 5 yearsYes, 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 over 4 years@Enthu sorry, long time I took the React road, too disconnected from Angular now ...
-
Mayur Patil over 3 yearsHostListener('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 almost 3 yearsHostlistener by default listens to the event type specified, you do not need to add it again on the specific component......
-
canbax over 2 years@mikegross what do you mean?
-
canbax over 2 yearsIt does work with
mouseenter
event but not withload
event. How can I call a component function when a certain div is loaded? -
Mathias about 2 yearsIt should be
new EventEmitter<boolean>()
instead ofnew EventEmitter<false>()