-
Notifications
You must be signed in to change notification settings - Fork 60
Various tweaks #28
base: master
Are you sure you want to change the base?
Various tweaks #28
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,8 +12,13 @@ import { | |
PLATFORM_ID, | ||
SimpleChanges, | ||
NgZone, | ||
Renderer2, | ||
} from '@angular/core'; | ||
import { isPlatformBrowser } from '@angular/common'; | ||
import { Observable } from 'rxjs/Observable'; | ||
import { Subject } from 'rxjs/Subject'; | ||
import { merge } from 'rxjs/observable/merge'; | ||
import { takeUntil } from 'rxjs/operators/takeUntil'; | ||
|
||
@Injectable() | ||
@Directive({ selector: '[clickOutside]' }) | ||
|
@@ -23,55 +28,62 @@ export class ClickOutsideDirective implements OnInit, OnChanges, OnDestroy { | |
@Input() delayClickOutsideInit: boolean = false; | ||
@Input() exclude: string = ''; | ||
@Input() excludeBeforeClick: boolean = false; | ||
@Input() clickOutsideEvents: string = ''; | ||
@Input() set clickOutsideEvents(events: string) { | ||
this._events = events.split(',').map(e => e.trim()); | ||
} | ||
@Input() clickOutsideEnabled: boolean = true; | ||
|
||
@Output() clickOutside: EventEmitter<Event> = new EventEmitter<Event>(); | ||
|
||
private _nodesExcluded: Array<HTMLElement> = []; | ||
private _events: Array<string> = ['click']; | ||
private _isPlatformBrowser: boolean = isPlatformBrowser(this.platformId); | ||
|
||
private _beforeInit: Subject<void> = new Subject<void>(); | ||
private _onDestroy: Subject<void> = new Subject<void>(); | ||
private _onOutsideClick: Subject<void> = new Subject<void>(); | ||
|
||
constructor(private _el: ElementRef, | ||
private _renderer2: Renderer2, | ||
private _ngZone: NgZone, | ||
@Inject(PLATFORM_ID) private platformId: Object) { | ||
this._initOnClickBody = this._initOnClickBody.bind(this); | ||
this._onClickBody = this._onClickBody.bind(this); | ||
} | ||
|
||
ngOnInit() { | ||
if (!isPlatformBrowser(this.platformId)) { return; } | ||
if (!this._isPlatformBrowser) { return; } | ||
|
||
this._init(); | ||
} | ||
|
||
ngOnDestroy() { | ||
if (!isPlatformBrowser(this.platformId)) { return; } | ||
if (!this._isPlatformBrowser) { return; } | ||
|
||
if (this.attachOutsideOnClick) { | ||
this._events.forEach(e => this._el.nativeElement.removeEventListener(e, this._initOnClickBody)); | ||
} | ||
this._onDestroy.next(); | ||
this._onDestroy.complete(); | ||
|
||
this._events.forEach(e => document.body.removeEventListener(e, this._onClickBody)); | ||
this._onOutsideClick.complete(); | ||
this._beforeInit.complete(); | ||
} | ||
|
||
ngOnChanges(changes: SimpleChanges) { | ||
if (!isPlatformBrowser(this.platformId)) { return; } | ||
if (!this._isPlatformBrowser) { return; } | ||
|
||
if (changes['attachOutsideOnClick'] || changes['exclude']) { | ||
this._init(); | ||
} | ||
} | ||
|
||
private _init() { | ||
if (this.clickOutsideEvents !== '') { | ||
this._events = this.clickOutsideEvents.split(',').map(e => e.trim()); | ||
} | ||
this._beforeInit.next(); | ||
|
||
this._excludeCheck(); | ||
|
||
if (this.attachOutsideOnClick) { | ||
this._ngZone.runOutsideAngular(() => { | ||
this._events.forEach(e => this._el.nativeElement.addEventListener(e, this._initOnClickBody)); | ||
this._listenAll(this._el.nativeElement, ...this._events) | ||
.subscribe(this._initOnClickBody); | ||
}); | ||
} else { | ||
this._initOnClickBody(); | ||
|
@@ -88,7 +100,11 @@ export class ClickOutsideDirective implements OnInit, OnChanges, OnDestroy { | |
|
||
private _initClickListeners() { | ||
this._ngZone.runOutsideAngular(() => { | ||
this._events.forEach(e => document.body.addEventListener(e, this._onClickBody)); | ||
this._listenAll('body', ...this._events) | ||
.pipe( | ||
takeUntil(this._onOutsideClick), | ||
) | ||
.subscribe(this._onClickBody); | ||
}); | ||
} | ||
|
||
|
@@ -118,18 +134,24 @@ export class ClickOutsideDirective implements OnInit, OnChanges, OnDestroy { | |
this._ngZone.run(() => this.clickOutside.emit(ev)); | ||
|
||
if (this.attachOutsideOnClick) { | ||
this._events.forEach(e => document.body.removeEventListener(e, this._onClickBody)); | ||
this._onOutsideClick.next(); | ||
} | ||
} | ||
} | ||
|
||
private _shouldExclude(target): boolean { | ||
for (let excludedNode of this._nodesExcluded) { | ||
if (excludedNode.contains(target)) { | ||
return true; | ||
} | ||
} | ||
return this._nodesExcluded.some(excludedNode => excludedNode.contains(target)); | ||
} | ||
|
||
private _listenAll(target: 'window' | 'document' | 'body' | any, ...eventNames: string[]): Observable<Event> { | ||
const sources = eventNames.map(eventName => { | ||
return new Observable<Event>(observer => this._renderer2.listen(target, eventName, ev => observer.next(ev))); | ||
}); | ||
|
||
return false; | ||
return merge(...sources) | ||
.pipe( | ||
takeUntil(this._beforeInit), | ||
takeUntil(this._onDestroy), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty unfamiliar with some rxjs practices. Could you explain what's going on here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this link will answer both your questions: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't you just add a |
||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of
_beforeInit
and_onDestroy
, exactly?