From 4d49218a993fd60b67d3a43f73f2489263fd0a35 Mon Sep 17 00:00:00 2001 From: Dragsaw Date: Wed, 3 Apr 2019 10:52:25 +0200 Subject: [PATCH] perf(dropdown): reduce the number of document click listeners (#4605) * perf(dropdown): reduce the number of document click listeners Edit document click listeners to be added only for open dropdowns which improves overall page performance in case of a large number of dropdowns. --- src/dropdown/bs-dropdown-toggle.directive.ts | 64 ++++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/dropdown/bs-dropdown-toggle.directive.ts b/src/dropdown/bs-dropdown-toggle.directive.ts index 1dfde3fb10..90ea4d65aa 100644 --- a/src/dropdown/bs-dropdown-toggle.directive.ts +++ b/src/dropdown/bs-dropdown-toggle.directive.ts @@ -1,12 +1,14 @@ import { + ChangeDetectorRef, Directive, ElementRef, HostBinding, HostListener, - OnDestroy + OnDestroy, + Renderer2 } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Subscription } from 'rxjs'; import { BsDropdownState } from './bs-dropdown.state'; import { BsDropdownDirective } from './bs-dropdown.directive'; @@ -19,19 +21,50 @@ import { BsDropdownDirective } from './bs-dropdown.directive'; }) export class BsDropdownToggleDirective implements OnDestroy { @HostBinding('attr.disabled') isDisabled: boolean = null; - - // @HostBinding('class.active') @HostBinding('attr.aria-expanded') isOpen: boolean; private _subscriptions: Subscription[] = []; + private _documentClickListener: Function; + private _escKeyUpListener: Function; - constructor(private _state: BsDropdownState, private _element: ElementRef, private dropdown: BsDropdownDirective) { + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _dropdown: BsDropdownDirective, + private _element: ElementRef, + private _renderer: Renderer2, + private _state: BsDropdownState + ) { // sync is open value with state this._subscriptions.push( this._state.isOpenChange.subscribe( - (value: boolean) => (this.isOpen = value) + (value: boolean) => { + this.isOpen = value; + + if (value) { + this._documentClickListener = this._renderer.listen('document', 'click', (event: any) => { + if (this._state.autoClose && event.button !== 2 && + !this._element.nativeElement.contains(event.target) && + !(this._state.insideClick && this._dropdown._contains(event)) + ) { + this._state.toggleClick.emit(false); + this._changeDetectorRef.detectChanges(); + } + }); + + this._escKeyUpListener = this._renderer.listen(this._element.nativeElement, 'keyup.esc', () => { + if (this._state.autoClose) { + this._state.toggleClick.emit(false); + this._changeDetectorRef.detectChanges(); + } + }); + } else { + this._documentClickListener(); + this._escKeyUpListener(); + } + } ) ); + // populate disabled state this._subscriptions.push( this._state.isDisabledChange.subscribe( @@ -48,25 +81,6 @@ export class BsDropdownToggleDirective implements OnDestroy { this._state.toggleClick.emit(true); } - @HostListener('document:click', ['$event']) - onDocumentClick(event: MouseEvent): void { - if ( - this._state.autoClose && - event.button !== 2 && - !this._element.nativeElement.contains(event.target) && - !(this._state.insideClick && this.dropdown._contains(event)) - ) { - this._state.toggleClick.emit(false); - } - } - - @HostListener('keyup.esc') - onEsc(): void { - if (this._state.autoClose) { - this._state.toggleClick.emit(false); - } - } - ngOnDestroy(): void { for (const sub of this._subscriptions) { sub.unsubscribe();