Datepicker Popup – Close when outside is clicked/touched

Bug description:

Datepicker popup does not close when an area outside of the popup/textbox is clicked/touched. Perhaps an optional true/false setting could be introduced to allow for this functionality.

Link to minimally-working plunker that reproduces the issue:

Datepicker popup on examples page

Version of Angular, ng-bootstrap, and Bootstrap:

Angular: 2.0.0 final

ng-bootstrap: 1.0.0-alpha.5

Bootstrap: 4.0.0-alpha.4

13 thoughts on “Datepicker Popup – Close when outside is clicked/touched

  1. What is the current status of this? As I see there is already commit, waiting to be included in release. Any reasons why is it not already included?

  2. @Asharma86 @pkozlowski-opensource
    #1623 is not the solution. Cause click other controllers in DOM should have the datePicker closed, too. Which #1623 ‘s solution does not, right?

    I changed it to

    if(!this._eref.nativeElement.querySelector('ngb-datepicker').contains(event.target)
        && !this._eref.nativeElement.querySelector('.input-group-addon').contains(event.target)) {
          let self = this;
          setTimeout(function(){
            self.dynamicId.close();
          },10);
        }
    

    but this is ugly.
    since the feature is the most basic feature for a datePicker,
    I really appreciate the master branch with the feature coming soon.

  3. Hi all,
    I tried this work around and it works well
    (click)=”d.toggle();$event.stopPropagation();” (document:click)=”d.close()”

  4. I was facing the same issue, as solution i write following code that worked for me without any use case failure and error, the code is given below,

    @HostListener(‘document:click’, [‘$event’])
    closeDatepickerOnclick(event) {
    if (event.target.offsetParent !== null && event.target.offsetParent.tagName !== ‘NGB-DATEPICKER’) {
    this.datepicker.close();
    }
    }

    Description : listen to the document click event and check that targeted offset parent element is not null and as well as its tag name is not equals to ‘NGB-DATEPICKER’ because outside click of datepicker you always get different offsetParent name than ‘NGB-DATEPICKER’ .

    Hope this helps someone.

  5. Here’s kind of working solution (based on https://github.com/pkozlowski-opensource/core/commit/11fdf895a09565616208fd812dd65a7f6dbe813d#diff-89266438c9fe2066d20a7ca712e768bf).
    The thing is it’ll only work with one toggle button. I suppose it’s possible to implement a service to connect all directives so they can be aware of other bindings.

    import { ComponentRef, Directive, ElementRef, HostListener, Input } from '@angular/core';
    import { NgbDatepicker, NgbInputDatepicker } from '@ng-bootstrap/ng-bootstrap';
    
    @Directive({
      selector: '[datepickerToggle]',
    })
    export class DatepickerToggleDirective {
      // tslint:disable-next-line no-input-rename
      @Input('datepickerToggle') inputDatepicker: NgbInputDatepicker;
      @Input() closeDatepickerOnClick: boolean = true;
    
      constructor(private _elementRef: ElementRef) {}
    
      @HostListener('click')
      public toggle() {
        if (this.inputDatepicker.isOpen() && this.closeDatepickerOnClick) {
          this.inputDatepicker.close();
        } else {
          this.inputDatepicker.open();
        }
      }
    
      @HostListener('document:click', ['$event'])
      public closeOnOutsideEvent($event: MouseEvent) {
        if (this.inputDatepicker.isOpen()) {
          if (!this.isTargettingToggleButton($event) && this.shouldCloseOnOutsideEvent($event)) {
            this.inputDatepicker.close();
          }
        }
      }
    
      private isTargettingToggleButton($event: MouseEvent): boolean {
        return this._elementRef.nativeElement.contains($event.target);
      }
    
      private shouldCloseOnOutsideEvent($event: MouseEvent): boolean {
        const inputDatepickerElRef: ElementRef = (this.inputDatepicker as any)._elRef;
        const datepickerCmpRef: ComponentRef<NgbDatepicker> = (this.inputDatepicker as any)._cRef;
    
        if (inputDatepickerElRef != null && datepickerCmpRef != null) {
          let popupClick = false;
          const inputClick = inputDatepickerElRef.nativeElement.contains($event.target);
    
          if (this.inputDatepicker.isOpen() && datepickerCmpRef.location.nativeElement.contains($event.target)) {
            popupClick = true;
          }
          return !inputClick && !popupClick;
        } else {
          return false;
        }
      }
    }

    Usage:

    <div class="input-group">
      <input
        class="form-control"
        [(ngModel)]="model"
        ngbDatepicker
        #datepicker="ngbDatepicker"
      >
      <div class="input-group-append">
        <button
          class="input-group-text"
          type="button"
          [datepickerToggle]="datepicker"
        >
          @
        </button>
      </div>
    </div>

    Add [closeDatepickerOnClick]="false" if you don’t want to close datepicker on second click.

  6. Probably not the best solution out there, but I managed to do it like this. I have several datepickers on that view, so I need to add the (document:click) on each one. @gpteamcs solution was almost perfect for me, but I could not use the arrows inside the datepicker without it closing, so I needed to refine it a bit more.

    <input style="background-color: white;" class="form-control" placeholder="YYYY-MM-DD" name="date" [(ngModel)]="date" ngbDatepicker #eToggle="ngbDatepicker" (click)="eToggle.toggle(); sToggle.close();$event.stopPropagation()" (document:click)="decideClosure($event, eToggle)" readonly>

    decideClosure(event, datepicker) { const path = event.path.map(p => p.localName); if (!path.includes('ngb-datepicker')) { datepicker.close(); } }