import {Directive, OnInit, OnDestroy, Output, EventEmitter, ElementRef} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/observable/fromEvent'; import 'rxjs/add/operator/delay'; import 'rxjs/add/operator/do'; @Directive({ selector: '[click-outside-or-esc]' }) export class ClickOutsideOrEsc implements OnInit, OnDestroy { private listening: boolean; private globalClick: any; private escClick: any; @Output('clickOutside') clickOutside: EventEmitter; constructor(private _elRef: ElementRef) { this.listening = false; this.clickOutside = new EventEmitter(); } ngOnInit() { this.globalClick = Observable .fromEvent(document, 'click') .delay(1) .do(() => { this.listening = true; }).subscribe((event: MouseEvent) => { this.onGlobalClick(event); }); this.escClick = Observable .fromEvent(document, 'keydown') .delay(1) .do(() => { this.listening = true; }).subscribe((event: KeyboardEvent) => { if(event.keyCode === 27) { this.clickOutside.emit({ target: (event.target || null), value: true }); } }); } ngOnDestroy() { this.globalClick.unsubscribe(); this.escClick.unsubscribe(); } onGlobalClick(event: MouseEvent) { if (event instanceof MouseEvent && this.listening === true) { if (this.isDescendant(this._elRef.nativeElement, event.target) === true) { this.clickOutside.emit({ target: (event.target || null), value: false }); } else { this.clickOutside.emit({ target: (event.target || null), value: true }); } } } isDescendant(parent, child) { let node = child; while (node !== null) { if (node === parent) { return true; } else { node = node.parentNode; } } return false; } }