Problem
In Angular, how can I detect clicks outside of a component?
Asked by AMagyar
Solution #1
import { Component, ElementRef, HostListener, Input } from '@angular/core';
@Component({
selector: 'selector',
template: `
<div>
{{text}}
</div>
`
})
export class AnotherComponent {
public text: String;
@HostListener('document:click', ['$event'])
clickout(event) {
if(this.eRef.nativeElement.contains(event.target)) {
this.text = "clicked inside";
} else {
this.text = "clicked outside";
}
}
constructor(private eRef: ElementRef) {
this.text = 'no clicks yet';
}
}
Click here to see a functional example.
Answered by AMagyar
Solution #2
A different perspective on AMagyar’s response. When you click on an element that is deleted from the DOM with a ngIf, this version works.
http://plnkr.co/edit/4mrn4GjM95uvSbQtxrAS?p=preview
Answered by J. Frankenstein
Solution #3
It is expensive to bind to a document click using @Hostlistener. If you overdo it, it can and will have an obvious performance impact (for example, when building a custom dropdown component and you have multiple instances created in a form).
Only apply a @Hostlistener() to the document click event once inside your main app component, in my opinion. The clicked target element’s value should be pushed into a public subject stored in a global utility service by the event.
@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(private utilitiesService: UtilitiesService) {}
@HostListener('document:click', ['$event'])
documentClick(event: any): void {
this.utilitiesService.documentClickedTarget.next(event.target)
}
}
@Injectable({ providedIn: 'root' })
export class UtilitiesService {
documentClickedTarget: Subject<HTMLElement> = new Subject<HTMLElement>()
}
Subscribe to the public subject of our utilities service if you’re interested in the clicked target element, then unsubscribe when the component is destroyed.
export class AnotherComponent implements OnInit {
@ViewChild('somePopup', { read: ElementRef, static: false }) somePopup: ElementRef
constructor(private utilitiesService: UtilitiesService) { }
ngOnInit() {
this.utilitiesService.documentClickedTarget
.subscribe(target => this.documentClickListener(target))
}
documentClickListener(target: any): void {
if (this.somePopup.nativeElement.contains(target))
// Clicked inside
else
// Clicked outside
}
Answered by ginalx
Solution #4
@J. Frankenstein’s answear is becoming better.
Answered by John Libes
Solution #5
Although the answers provided above are valid, what if you are doing a time-consuming operation after losing concentration on the relevant component? For that, I devised a solution based on two flags, in which the focus out event is triggered only when the focus is lost from the relevant component.
isFocusInsideComponent = false;
isComponentClicked = false;
@HostListener('click')
clickInside() {
this.isFocusInsideComponent = true;
this.isComponentClicked = true;
}
@HostListener('document:click')
clickout() {
if (!this.isFocusInsideComponent && this.isComponentClicked) {
// do the heavy process
this.isComponentClicked = false;
}
this.isFocusInsideComponent = false;
}
I hope this has been of assistance to you. If I’ve made any mistakes, please let me know.
Answered by Rishanthakumar
Post is based on https://stackoverflow.com/questions/40107008/detect-click-outside-angular-component