From 9971faa7d9c54db40c19fa6333dce8a65d38ccd4 Mon Sep 17 00:00:00 2001 From: Artur Androsovych Date: Thu, 13 Jan 2022 13:23:37 +0200 Subject: [PATCH] perf(module:input-number): reduce change detection cycles (#7129) --- .../input-number/input-number.component.ts | 56 ++++++++++++------- components/input-number/input-number.spec.ts | 35 +++++++++++- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/components/input-number/input-number.component.ts b/components/input-number/input-number.component.ts index 7cf09aa2a3..f15665e01f 100644 --- a/components/input-number/input-number.component.ts +++ b/components/input-number/input-number.component.ts @@ -15,6 +15,7 @@ import { EventEmitter, forwardRef, Input, + NgZone, OnChanges, OnDestroy, OnInit, @@ -25,7 +26,7 @@ import { ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { Subject } from 'rxjs'; +import { fromEvent, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { BooleanInput, NzSizeLDSType, OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types'; @@ -70,8 +71,6 @@ import { InputBoolean, isNotNil } from 'ng-zorro-antd/core/util'; [placeholder]="nzPlaceHolder" [attr.step]="nzStep" [attr.inputmode]="nzInputMode" - (keydown)="onKeyDown($event)" - (keyup)="stop()" [ngModel]="displayValue" (ngModelChange)="onModelChange($event)" /> @@ -336,20 +335,6 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn this.inputElement.nativeElement.value = `${displayValue}`; } - onKeyDown(e: KeyboardEvent): void { - if (e.keyCode === UP_ARROW) { - const ratio = this.getRatio(e); - this.up(e, ratio); - this.stop(); - } else if (e.keyCode === DOWN_ARROW) { - const ratio = this.getRatio(e); - this.down(e, ratio); - this.stop(); - } else if (e.keyCode === ENTER) { - this.updateDisplayValue(this.value!); - } - } - writeValue(value: number): void { this.value = value; this.setValue(value); @@ -379,7 +364,8 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn } constructor( - private elementRef: ElementRef, + private ngZone: NgZone, + private elementRef: ElementRef, private cdr: ChangeDetectorRef, private focusMonitor: FocusMonitor, @Optional() private directionality: Directionality @@ -402,9 +388,41 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn }); this.dir = this.directionality.value; - this.directionality.change?.pipe(takeUntil(this.destroy$)).subscribe((direction: Direction) => { + this.directionality.change.pipe(takeUntil(this.destroy$)).subscribe((direction: Direction) => { this.dir = direction; }); + + this.ngZone.runOutsideAngular(() => { + fromEvent(this.inputElement.nativeElement, 'keyup') + .pipe(takeUntil(this.destroy$)) + .subscribe(() => this.stop()); + + fromEvent(this.inputElement.nativeElement, 'keydown') + .pipe(takeUntil(this.destroy$)) + .subscribe(event => { + const { keyCode } = event; + + if (keyCode !== UP_ARROW && keyCode !== DOWN_ARROW && keyCode !== ENTER) { + return; + } + + this.ngZone.run(() => { + if (keyCode === UP_ARROW) { + const ratio = this.getRatio(event); + this.up(event, ratio); + this.stop(); + } else if (keyCode === DOWN_ARROW) { + const ratio = this.getRatio(event); + this.down(event, ratio); + this.stop(); + } else { + this.updateDisplayValue(this.value!); + } + + this.cdr.markForCheck(); + }); + }); + }); } ngOnChanges(changes: SimpleChanges): void { diff --git a/components/input-number/input-number.spec.ts b/components/input-number/input-number.spec.ts index b8d159dace..d916129557 100644 --- a/components/input-number/input-number.spec.ts +++ b/components/input-number/input-number.spec.ts @@ -1,8 +1,9 @@ -import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes'; -import { Component, DebugElement, ViewChild } from '@angular/core'; +import { DOWN_ARROW, ENTER, TAB, UP_ARROW } from '@angular/cdk/keycodes'; +import { ApplicationRef, Component, DebugElement, NgZone, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; +import { take } from 'rxjs/operators'; import { createKeyboardEvent, dispatchEvent, dispatchFakeEvent } from 'ng-zorro-antd/core/testing'; @@ -395,6 +396,36 @@ describe('input number', () => { fixture.detectChanges(); expect(inputNumber.nativeElement.classList).not.toContain('ant-input-number-focused'); }); + describe('change detection behavior', () => { + it('should not run change detection on keyup and keydown events', done => { + const ngZone = TestBed.inject(NgZone); + const appRef = TestBed.inject(ApplicationRef); + spyOn(appRef, 'tick'); + spyOn(inputNumber.componentInstance, 'stop').and.callThrough(); + + inputElement.dispatchEvent(new KeyboardEvent('keyup')); + expect(appRef.tick).toHaveBeenCalledTimes(0); + expect(inputNumber.componentInstance.stop).toHaveBeenCalled(); + + inputElement.dispatchEvent( + new KeyboardEvent('keydown', { + keyCode: TAB + }) + ); + expect(appRef.tick).toHaveBeenCalledTimes(0); + + inputElement.dispatchEvent( + new KeyboardEvent('keydown', { + keyCode: ENTER + }) + ); + + ngZone.onMicrotaskEmpty.pipe(take(1)).subscribe(() => { + expect(appRef.tick).toHaveBeenCalledTimes(1); + done(); + }); + }); + }); }); describe('input number form', () => {