Coder Perfect

Outside of the Angular component, detect a click

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