diff --git a/components/input-number/input-number.component.ts b/components/input-number/input-number.component.ts index 8c8af32fe2..0545763fed 100644 --- a/components/input-number/input-number.component.ts +++ b/components/input-number/input-number.component.ts @@ -26,9 +26,10 @@ import { ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { fromEvent, Subject } from 'rxjs'; +import { fromEvent, merge } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { NzDestroyService } from 'ng-zorro-antd/core/services'; import { BooleanInput, NzSizeLDSType, OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types'; import { InputBoolean, isNotNil } from 'ng-zorro-antd/core/util'; @@ -38,21 +39,19 @@ import { InputBoolean, isNotNil } from 'ng-zorro-antd/core/util'; template: `
@@ -82,7 +81,8 @@ import { InputBoolean, isNotNil } from 'ng-zorro-antd/core/util'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NzInputNumberComponent), multi: true - } + }, + NzDestroyService ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, @@ -104,7 +104,6 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn private autoStepTimer?: number; private parsedValue?: string | number; private value?: number; - private destroy$ = new Subject(); displayValue?: string | number; isFocused = false; disabledUp = false; @@ -114,6 +113,11 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn onTouched: OnTouchedType = () => {}; @Output() readonly nzBlur = new EventEmitter(); @Output() readonly nzFocus = new EventEmitter(); + /** The native `` element. */ + @ViewChild('upHandler', { static: true }) upHandler!: ElementRef; + /** The native `` element. */ + @ViewChild('downHandler', { static: true }) downHandler!: ElementRef; + /** The native `` element. */ @ViewChild('inputElement', { static: true }) inputElement!: ElementRef; @Input() nzSize: NzSizeLDSType = 'default'; @Input() nzMin: number = -Infinity; @@ -372,30 +376,30 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn private elementRef: ElementRef, private cdr: ChangeDetectorRef, private focusMonitor: FocusMonitor, - @Optional() private directionality: Directionality + @Optional() private directionality: Directionality, + private destroy$: NzDestroyService ) {} ngOnInit(): void { - this.focusMonitor - .monitor(this.elementRef, true) - .pipe(takeUntil(this.destroy$)) - .subscribe(focusOrigin => { - if (!focusOrigin) { - this.isFocused = false; - this.updateDisplayValue(this.value!); - this.nzBlur.emit(); - Promise.resolve().then(() => this.onTouched()); - } else { - this.isFocused = true; - this.nzFocus.emit(); - } - }); + this.focusMonitor.monitor(this.elementRef, true).subscribe(focusOrigin => { + if (!focusOrigin) { + this.isFocused = false; + this.updateDisplayValue(this.value!); + this.nzBlur.emit(); + Promise.resolve().then(() => this.onTouched()); + } else { + this.isFocused = true; + this.nzFocus.emit(); + } + }); this.dir = this.directionality.value; this.directionality.change.pipe(takeUntil(this.destroy$)).subscribe((direction: Direction) => { this.dir = direction; }); + this.setupHandlersListeners(); + this.ngZone.runOutsideAngular(() => { fromEvent(this.inputElement.nativeElement, 'keyup') .pipe(takeUntil(this.destroy$)) @@ -445,7 +449,18 @@ export class NzInputNumberComponent implements ControlValueAccessor, AfterViewIn ngOnDestroy(): void { this.focusMonitor.stopMonitoring(this.elementRef); - this.destroy$.next(); - this.destroy$.complete(); + } + + private setupHandlersListeners(): void { + this.ngZone.runOutsideAngular(() => { + merge( + fromEvent(this.upHandler.nativeElement, 'mouseup'), + fromEvent(this.upHandler.nativeElement, 'mouseleave'), + fromEvent(this.downHandler.nativeElement, 'mouseup'), + fromEvent(this.downHandler.nativeElement, 'mouseleave') + ) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => this.stop()); + }); } } diff --git a/components/input-number/input-number.spec.ts b/components/input-number/input-number.spec.ts index 711356c936..8528dce164 100644 --- a/components/input-number/input-number.spec.ts +++ b/components/input-number/input-number.spec.ts @@ -5,7 +5,7 @@ import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angul import { By } from '@angular/platform-browser'; import { take } from 'rxjs/operators'; -import { createKeyboardEvent, dispatchEvent, dispatchFakeEvent } from 'ng-zorro-antd/core/testing'; +import { createKeyboardEvent, createMouseEvent, dispatchEvent, dispatchFakeEvent } from 'ng-zorro-antd/core/testing'; import { NzInputNumberComponent } from './input-number.component'; import { NzInputNumberModule } from './input-number.module'; @@ -429,6 +429,24 @@ describe('input number', () => { done(); }); }); + it('should not run change detection when `mouseup` and `mouseleave` events are dispatched on handlers', () => { + const appRef = TestBed.inject(ApplicationRef); + spyOn(appRef, 'tick'); + spyOn(inputNumber.componentInstance, 'stop').and.callThrough(); + + const mouseupEvent = createMouseEvent('mouseup'); + const mouseleaveEvent = createMouseEvent('mouseleave'); + + upHandler.dispatchEvent(mouseupEvent); + upHandler.dispatchEvent(mouseleaveEvent); + + downHandler.dispatchEvent(mouseupEvent); + downHandler.dispatchEvent(mouseleaveEvent); + + expect(appRef.tick).not.toHaveBeenCalled(); + // We have dispatched 4 events that are followed by calling `stop()`. + expect(inputNumber.componentInstance.stop).toHaveBeenCalledTimes(4); + }); }); });