Skip to content

Commit fd63d22

Browse files
authored
perf(module:select): do not run change detection unnecessarily on click events (#7175)
1 parent 7d091bb commit fd63d22

File tree

1 file changed

+32
-22
lines changed

1 file changed

+32
-22
lines changed

Diff for: components/select/select.component.ts

+32-22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
forwardRef,
2020
Host,
2121
Input,
22+
NgZone,
2223
OnChanges,
2324
OnDestroy,
2425
OnInit,
@@ -31,7 +32,7 @@ import {
3132
ViewEncapsulation
3233
} from '@angular/core';
3334
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
34-
import { BehaviorSubject, combineLatest, merge } from 'rxjs';
35+
import { BehaviorSubject, combineLatest, fromEvent, merge } from 'rxjs';
3536
import { startWith, switchMap, takeUntil } from 'rxjs/operators';
3637

3738
import { slideMotion } from 'ng-zorro-antd/core/animation';
@@ -119,7 +120,6 @@ export type NzSelectSizeType = 'large' | 'default' | 'small';
119120
[cdkConnectedOverlayTransformOriginOn]="'.ant-select-dropdown'"
120121
[cdkConnectedOverlayPanelClass]="nzDropdownClassName!"
121122
[cdkConnectedOverlayOpen]="nzOpen"
122-
(overlayKeydown)="onOverlayKeyDown($event)"
123123
(overlayOutsideClick)="onClickOutside($event)"
124124
(detach)="setOpenState(false)"
125125
(positionChange)="onPositionChange($event)"
@@ -161,8 +161,7 @@ export type NzSelectSizeType = 'large' | 'default' | 'small';
161161
'[class.ant-select-focused]': 'nzOpen || focused',
162162
'[class.ant-select-single]': `nzMode === 'default'`,
163163
'[class.ant-select-multiple]': `nzMode !== 'default'`,
164-
'[class.ant-select-rtl]': `dir === 'rtl'`,
165-
'(click)': 'onHostClick()'
164+
'[class.ant-select-rtl]': `dir === 'rtl'`
166165
}
167166
})
168167
export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterContentInit, OnChanges, OnDestroy {
@@ -292,14 +291,6 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
292291
this.clearInput();
293292
}
294293

295-
onHostClick(): void {
296-
if ((this.nzOpen && this.nzShowSearch) || this.nzDisabled) {
297-
return;
298-
}
299-
300-
this.setOpenState(!this.nzOpen);
301-
}
302-
303294
updateListOfContainerItem(): void {
304295
let listOfContainerItem = this.listOfTagAndTemplateItem
305296
.filter(item => !item.nzHide)
@@ -384,12 +375,6 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
384375
this.clearInput();
385376
}
386377

387-
onOverlayKeyDown(e: KeyboardEvent): void {
388-
if (e.keyCode === ESCAPE) {
389-
this.setOpenState(false);
390-
}
391-
}
392-
393378
onKeyDown(e: KeyboardEvent): void {
394379
if (this.nzDisabled) {
395380
return;
@@ -474,7 +459,7 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
474459
}
475460

476461
onClickOutside(event: MouseEvent): void {
477-
if (!this.elementRef.nativeElement.contains(event.target)) {
462+
if (!this.host.nativeElement.contains(event.target as HTMLElement)) {
478463
this.setOpenState(false);
479464
}
480465
}
@@ -516,10 +501,11 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
516501
}
517502

518503
constructor(
504+
private ngZone: NgZone,
519505
private destroy$: NzDestroyService,
520506
public nzConfigService: NzConfigService,
521507
private cdr: ChangeDetectorRef,
522-
private elementRef: ElementRef,
508+
private host: ElementRef<HTMLElement>,
523509
private platform: Platform,
524510
private focusMonitor: FocusMonitor,
525511
@Optional() private directionality: Directionality,
@@ -592,7 +578,7 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
592578

593579
ngOnInit(): void {
594580
this.focusMonitor
595-
.monitor(this.elementRef, true)
581+
.monitor(this.host, true)
596582
.pipe(takeUntil(this.destroy$))
597583
.subscribe(focusOrigin => {
598584
if (!focusOrigin) {
@@ -640,6 +626,29 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
640626
});
641627

642628
this.dir = this.directionality.value;
629+
630+
this.ngZone.runOutsideAngular(() =>
631+
fromEvent(this.host.nativeElement, 'click')
632+
.pipe(takeUntil(this.destroy$))
633+
.subscribe(() => {
634+
if ((this.nzOpen && this.nzShowSearch) || this.nzDisabled) {
635+
return;
636+
}
637+
638+
this.ngZone.run(() => this.setOpenState(!this.nzOpen));
639+
})
640+
);
641+
642+
// Caretaker note: we could've added this listener within the template `(overlayKeydown)="..."`,
643+
// but with this approach, it'll run change detection on each keyboard click, and also it'll run
644+
// `markForCheck()` internally, which means the whole component tree (starting from the root and
645+
// going down to the select component) will be re-checked and updated (if needed).
646+
// This is safe to do that manually since `setOpenState()` calls `markForCheck()` if needed.
647+
this.cdkConnectedOverlay.overlayKeydown.pipe(takeUntil(this.destroy$)).subscribe(event => {
648+
if (event.keyCode === ESCAPE) {
649+
this.setOpenState(false);
650+
}
651+
});
643652
}
644653

645654
ngAfterContentInit(): void {
@@ -679,8 +688,9 @@ export class NzSelectComponent implements ControlValueAccessor, OnInit, AfterCon
679688
});
680689
}
681690
}
691+
682692
ngOnDestroy(): void {
683693
cancelRequestAnimationFrame(this.requestId);
684-
this.focusMonitor.stopMonitoring(this.elementRef);
694+
this.focusMonitor.stopMonitoring(this.host);
685695
}
686696
}

0 commit comments

Comments
 (0)