Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I close a dropdown on click outside?

People also ask

How do I hide a dropdown?

You can hide the dropdown arrow from the DropDownButton by adding class e-caret-hide to DropDownButton element using cssClass property.


You can use (document:click) event:

@Component({
  host: {
    '(document:click)': 'onClick($event)',
  },
})
class SomeComponent() {
  constructor(private _eref: ElementRef) { }

  onClick(event) {
   if (!this._eref.nativeElement.contains(event.target)) // or some similar check
     doSomething();
  }
}

Another approach is to create custom event as a directive. Check out these posts by Ben Nadel:

  • tracking-click-events-outside-the-current-component
  • selectors-and-outputs-can-have-the-same-name
  • DirectiveMetadata
  • Host Binding

ELEGANT METHOD

I found this clickOut directive: https://github.com/chliebel/angular2-click-outside. I check it and it works well (i only copy clickOutside.directive.ts to my project). U can use it in this way:

<div (clickOutside)="close($event)"></div>

Where close is your function which will be call when user click outside div. It is very elegant way to handle problem described in question.

If you use above directive to close popUp window, remember first to add event.stopPropagation() to button click event handler which open popUp.

BONUS:

Below i copy oryginal directive code from file clickOutside.directive.ts (in case if link will stop working in future) - the author is Christian Liebel :

import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core';
    
@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {
    constructor(private _elementRef: ElementRef) {
    }

    @Output()
    public clickOutside = new EventEmitter<MouseEvent>();

    @HostListener('document:click', ['$event', '$event.target'])
    public onClick(event: MouseEvent, targetElement: HTMLElement): void {
        if (!targetElement) {
            return;
        }

        const clickedInside = this._elementRef.nativeElement.contains(targetElement);
        if (!clickedInside) {
            this.clickOutside.emit(event);
        }
    }
}

I've done it this way.

Added an event listener on document click and in that handler checked if my container contains event.target, if not - hide the dropdown.

It would look like this.

@Component({})
class SomeComponent {
    @ViewChild('container') container;
    @ViewChild('dropdown') dropdown;

    constructor() {
        document.addEventListener('click', this.offClickHandler.bind(this)); // bind on doc
    }

    offClickHandler(event:any) {
        if (!this.container.nativeElement.contains(event.target)) { // check click origin
            this.dropdown.nativeElement.style.display = "none";
        }
    }
}

I think Sasxa accepted answer works for most people. However, I had a situation, where the content of the Element, that should listen to off-click events, changed dynamically. So the Elements nativeElement did not contain the event.target, when it was created dynamically. I could solve this with the following directive

@Directive({
  selector: '[myOffClick]'
})
export class MyOffClickDirective {

  @Output() offClick = new EventEmitter();

  constructor(private _elementRef: ElementRef) {
  }

  @HostListener('document:click', ['$event.path'])
  public onGlobalClick(targetElementPath: Array<any>) {
    let elementRefInPath = targetElementPath.find(e => e === this._elementRef.nativeElement);
    if (!elementRefInPath) {
      this.offClick.emit(null);
    }
  }
}

Instead of checking if elementRef contains event.target, I check if elementRef is in the path (DOM path to target) of the event. That way it is possible to handle dynamically created Elements.


If you're doing this on iOS, use the touchstart event as well:

As of Angular 4, the HostListener decorate is the preferred way to do this

import { Component, OnInit, HostListener, ElementRef } from '@angular/core';
...
@Component({...})
export class MyComponent implement OnInit {

  constructor(private eRef: ElementRef){}

  @HostListener('document:click', ['$event'])
  @HostListener('document:touchstart', ['$event'])
  handleOutsideClick(event) {
    // Some kind of logic to exclude clicks in Component.
    // This example is borrowed Kamil's answer
    if (!this.eRef.nativeElement.contains(event.target) {
      doSomethingCool();
    }
  }

}

We've been working on a similar issue at work today, trying to figure out how to make a dropdown div disappear when it is clicked off of. Ours is slightly different than the initial poster's question because we didn't want to click away from a different component or directive, but merely outside of the particular div.

We ended up solving it by using the (window:mouseup) event handler.

Steps:
1.) We gave the entire dropdown menu div a unique class name.

2.) On the inner dropdown menu itself (the only portion that we wanted clicks to NOT close the menu), we added a (window:mouseup) event handler and passed in the $event.

NOTE: It could not be done with a typical "click" handler because this conflicted with the parent click handler.

3.) In our controller, we created the method that we wanted to be called on the click out event, and we use the event.closest (docs here) to find out if the clicked spot is within our targeted-class div.

 autoCloseForDropdownCars(event) {
        var target = event.target;
        if (!target.closest(".DropdownCars")) { 
            // do whatever you want here
        }
    }
 <div class="DropdownCars">
   <span (click)="toggleDropdown(dropdownTypes.Cars)" class="searchBarPlaceholder">Cars</span>
   <div class="criteriaDropdown" (window:mouseup)="autoCloseForDropdownCars($event)" *ngIf="isDropdownShown(dropdownTypes.Cars)">
   </div>
</div>

I didn't make any workaround. I've just attached document:click on my toggle function as follow :


    @Directive({
      selector: '[appDropDown]'
    })
    export class DropdownDirective implements OnInit {

      @HostBinding('class.open') isOpen: boolean;

      constructor(private elemRef: ElementRef) { }

      ngOnInit(): void {
        this.isOpen = false;
      }

      @HostListener('document:click', ['$event'])
      @HostListener('document:touchstart', ['$event'])
      toggle(event) {
        if (this.elemRef.nativeElement.contains(event.target)) {
          this.isOpen = !this.isOpen;
        } else {
          this.isOpen = false;
      }
    }

So, when I am outside my directive, I close the dropdown.