Skip to content

Instantly share code, notes, and snippets.

@saravanapriyanm
Created August 31, 2021 11:31
Show Gist options
  • Save saravanapriyanm/fbedc453a063aa17fa2cf2241add23ac to your computer and use it in GitHub Desktop.
Save saravanapriyanm/fbedc453a063aa17fa2cf2241add23ac to your computer and use it in GitHub Desktop.
import {
Directive,
ElementRef,
EventEmitter,
Inject,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
PLATFORM_ID,
SimpleChanges,
NgZone,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
// tslint:disable-next-line: directive-selector
@Directive({ selector: '[clickOutside]' })
export class ClickOutsideDirective implements OnInit, OnChanges, OnDestroy {
@Input() clickOutsideEnabled = true;
@Input() attachOutsideOnClick = false;
@Input() delayClickOutsideInit = false;
@Input() emitOnBlur = false;
@Input() exclude = '';
@Input() excludeBeforeClick = false;
@Input() clickOutsideEvents = '';
@Output() clickOutside: EventEmitter<Event> = new EventEmitter<Event>();
private nodesExcluded: Array<HTMLElement> = [];
private events: Array<string> = ['click'];
constructor(
private el: ElementRef,
private ngZone: NgZone,
@Inject(PLATFORM_ID) private platformId: object) {
this._initOnClickBody = this._initOnClickBody.bind(this);
this._onClickBody = this._onClickBody.bind(this);
this._onWindowBlur = this._onWindowBlur.bind(this);
}
ngOnInit() {
if (!isPlatformBrowser(this.platformId)) { return; }
this._init();
}
ngOnDestroy() {
if (!isPlatformBrowser(this.platformId)) { return; }
this._removeClickOutsideListener();
this._removeAttachOutsideOnClickListener();
this._removeWindowBlurListener();
}
ngOnChanges(changes: SimpleChanges) {
if (!isPlatformBrowser(this.platformId)) { return; }
if (changes.attachOutsideOnClick || changes.exclude || changes.emitOnBlur) {
this._init();
}
}
private _init() {
if (this.clickOutsideEvents !== '') {
this.events = this.clickOutsideEvents.split(',').map(e => e.trim());
}
this._excludeCheck();
if (this.attachOutsideOnClick) {
this._initAttachOutsideOnClickListener();
} else {
this._initOnClickBody();
}
if (this.emitOnBlur) {
this._initWindowBlurListener();
}
}
private _initOnClickBody() {
if (this.delayClickOutsideInit) {
setTimeout(this._initClickOutsideListener.bind(this));
} else {
this._initClickOutsideListener();
}
}
private _excludeCheck() {
if (this.exclude) {
try {
const nodes = Array.from(document.querySelectorAll(this.exclude)) as Array<HTMLElement>;
if (nodes) {
this.nodesExcluded = nodes;
}
} catch (err) {
console.error('[ng-click-outside] Check your exclude selector syntax.', err);
}
}
}
private _onClickBody(ev: Event) {
if (!this.clickOutsideEnabled) { return; }
if (this.excludeBeforeClick) {
this._excludeCheck();
}
if (!this.el.nativeElement.contains(ev.target) && !this._shouldExclude(ev.target)) {
this._emit(ev);
if (this.attachOutsideOnClick) {
this._removeClickOutsideListener();
}
}
}
/**
* Resolves problem with outside click on iframe
* @see https://github.com/arkon/ng-click-outside/issues/32
*/
private _onWindowBlur(ev: Event) {
setTimeout(() => {
if (!document.hidden) {
this._emit(ev);
}
});
}
private _emit(ev: Event) {
if (!this.clickOutsideEnabled) { return; }
this.ngZone.run(() => this.clickOutside.emit(ev));
}
private _shouldExclude(target): boolean {
for (const excludedNode of this.nodesExcluded) {
if (excludedNode.contains(target)) {
return true;
}
}
return false;
}
private _initClickOutsideListener() {
this.ngZone.runOutsideAngular(() => {
this.events.forEach(e => document.addEventListener(e, this._onClickBody));
});
}
private _removeClickOutsideListener() {
this.ngZone.runOutsideAngular(() => {
this.events.forEach(e => document.removeEventListener(e, this._onClickBody));
});
}
private _initAttachOutsideOnClickListener() {
this.ngZone.runOutsideAngular(() => {
this.events.forEach(e => this.el.nativeElement.addEventListener(e, this._initOnClickBody));
});
}
private _removeAttachOutsideOnClickListener() {
this.ngZone.runOutsideAngular(() => {
this.events.forEach(e => this.el.nativeElement.removeEventListener(e, this._initOnClickBody));
});
}
private _initWindowBlurListener() {
this.ngZone.runOutsideAngular(() => {
window.addEventListener('blur', this._onWindowBlur);
});
}
private _removeWindowBlurListener() {
this.ngZone.runOutsideAngular(() => {
window.removeEventListener('blur', this._onWindowBlur);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment