diff --git a/components/resizable/resizable.directive.ts b/components/resizable/resizable.directive.ts index 34c6b9c6ea..ba8fd38d45 100644 --- a/components/resizable/resizable.directive.ts +++ b/components/resizable/resizable.directive.ts @@ -15,9 +15,10 @@ import { Output, Renderer2 } from '@angular/core'; -import { fromEvent, Subject } from 'rxjs'; +import { fromEvent } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { NzDestroyService } from 'ng-zorro-antd/core/services'; import { BooleanInput } from 'ng-zorro-antd/core/types'; import { ensureInBounds, InputBoolean } from 'ng-zorro-antd/core/util'; @@ -35,7 +36,7 @@ export interface NzResizeEvent { @Directive({ selector: '[nz-resizable]', exportAs: 'nzResizable', - providers: [NzResizableService], + providers: [NzResizableService, NzDestroyService], host: { class: 'nz-resizable', '[class.nz-resizable-resizing]': 'resizing', @@ -68,16 +69,16 @@ export class NzResizableDirective implements AfterViewInit, OnDestroy { private ghostElement: HTMLDivElement | null = null; private el!: HTMLElement; private sizeCache: NzResizeEvent | null = null; - private destroy$ = new Subject(); constructor( private elementRef: ElementRef, private renderer: Renderer2, private nzResizableService: NzResizableService, private platform: Platform, - private ngZone: NgZone + private ngZone: NgZone, + private destroy$: NzDestroyService ) { - this.nzResizableService.handleMouseDown$.pipe(takeUntil(this.destroy$)).subscribe(event => { + this.nzResizableService.handleMouseDownOutsideAngular$.pipe(takeUntil(this.destroy$)).subscribe(event => { if (this.nzDisabled) { return; } @@ -85,21 +86,21 @@ export class NzResizableDirective implements AfterViewInit, OnDestroy { this.nzResizableService.startResizing(event.mouseEvent); this.currentHandleEvent = event; this.setCursor(); - this.nzResizeStart.emit({ - mouseEvent: event.mouseEvent - }); + if (this.nzResizeStart.observers.length) { + this.ngZone.run(() => this.nzResizeStart.emit({ mouseEvent: event.mouseEvent })); + } this.elRect = this.el.getBoundingClientRect(); }); - this.nzResizableService.documentMouseUp$.pipe(takeUntil(this.destroy$)).subscribe(event => { + this.nzResizableService.documentMouseUpOutsideAngular$.pipe(takeUntil(this.destroy$)).subscribe(event => { if (this.resizing) { this.resizing = false; - this.nzResizableService.documentMouseUp$.next(); + this.nzResizableService.documentMouseUpOutsideAngular$.next(); this.endResize(event); } }); - this.nzResizableService.documentMouseMove$.pipe(takeUntil(this.destroy$)).subscribe(event => { + this.nzResizableService.documentMouseMoveOutsideAngular$.pipe(takeUntil(this.destroy$)).subscribe(event => { if (this.resizing) { this.resize(event); } @@ -312,13 +313,13 @@ export class NzResizableDirective implements AfterViewInit, OnDestroy { fromEvent(this.el, 'mouseenter') .pipe(takeUntil(this.destroy$)) .subscribe(() => { - this.nzResizableService.mouseEntered$.next(true); + this.nzResizableService.mouseEnteredOutsideAngular$.next(true); }); fromEvent(this.el, 'mouseleave') .pipe(takeUntil(this.destroy$)) .subscribe(() => { - this.nzResizableService.mouseEntered$.next(false); + this.nzResizableService.mouseEnteredOutsideAngular$.next(false); }); }); } @@ -326,7 +327,5 @@ export class NzResizableDirective implements AfterViewInit, OnDestroy { ngOnDestroy(): void { this.ghostElement = null; this.sizeCache = null; - this.destroy$.next(); - this.destroy$.complete(); } } diff --git a/components/resizable/resizable.service.ts b/components/resizable/resizable.service.ts index 60d9206201..36e439e028 100644 --- a/components/resizable/resizable.service.ts +++ b/components/resizable/resizable.service.ts @@ -17,10 +17,20 @@ export class NzResizableService implements OnDestroy { private document: Document; private listeners = new Map void>(); - handleMouseDown$ = new Subject(); - documentMouseUp$ = new Subject(); - documentMouseMove$ = new Subject(); - mouseEntered$ = new Subject(); + /** + * The `OutsideAngular` prefix means that the subject will emit events outside of the Angular zone, + * so that becomes a bit more descriptive for those who'll maintain the code in the future: + * ```ts + * nzResizableService.handleMouseDownOutsideAngular$.subscribe(event => { + * console.log(Zone.current); // + * console.log(NgZone.isInAngularZone()); // false + * }); + * ``` + */ + handleMouseDownOutsideAngular$ = new Subject(); + documentMouseUpOutsideAngular$ = new Subject(); + documentMouseMoveOutsideAngular$ = new Subject(); + mouseEnteredOutsideAngular$ = new Subject(); constructor(private ngZone: NgZone, @Inject(DOCUMENT) document: NzSafeAny) { this.document = document; @@ -32,10 +42,10 @@ export class NzResizableService implements OnDestroy { const moveEvent = _isTouchEvent ? 'touchmove' : 'mousemove'; const upEvent = _isTouchEvent ? 'touchend' : 'mouseup'; const moveEventHandler = (e: MouseEvent | TouchEvent): void => { - this.documentMouseMove$.next(e); + this.documentMouseMoveOutsideAngular$.next(e); }; const upEventHandler = (e: MouseEvent | TouchEvent): void => { - this.documentMouseUp$.next(e); + this.documentMouseUpOutsideAngular$.next(e); this.clearListeners(); }; @@ -57,10 +67,10 @@ export class NzResizableService implements OnDestroy { } ngOnDestroy(): void { - this.handleMouseDown$.complete(); - this.documentMouseUp$.complete(); - this.documentMouseMove$.complete(); - this.mouseEntered$.complete(); + this.handleMouseDownOutsideAngular$.complete(); + this.documentMouseUpOutsideAngular$.complete(); + this.documentMouseMoveOutsideAngular$.complete(); + this.mouseEnteredOutsideAngular$.complete(); this.clearListeners(); } } diff --git a/components/resizable/resizable.spec.ts b/components/resizable/resizable.spec.ts index 1d8a2b3674..c460d20020 100644 --- a/components/resizable/resizable.spec.ts +++ b/components/resizable/resizable.spec.ts @@ -77,6 +77,14 @@ describe('resizable', () => { expect(appRef.tick).toHaveBeenCalledTimes(0); }); + it('should not run change detection on `mousedown` event on the `nz-resize-handle`', () => { + const appRef = TestBed.inject(ApplicationRef); + spyOn(appRef, 'tick'); + const nzResizeHandle = resizableEle.querySelector('nz-resize-handle')!; + dispatchMouseEvent(nzResizeHandle, 'mousedown'); + expect(appRef.tick).toHaveBeenCalledTimes(0); + }); + it('should maximum size work', fakeAsync(() => { const rect = resizableEle.getBoundingClientRect(); const handel = resizableEle.querySelector('.nz-resizable-handle-bottomRight') as HTMLElement; diff --git a/components/resizable/resize-handle.component.ts b/components/resizable/resize-handle.component.ts index 0cd10cc4a7..55bdf3e04c 100644 --- a/components/resizable/resize-handle.component.ts +++ b/components/resizable/resize-handle.component.ts @@ -9,14 +9,16 @@ import { ElementRef, EventEmitter, Input, - OnDestroy, + NgZone, OnInit, Output, Renderer2 } from '@angular/core'; -import { Subject } from 'rxjs'; +import { fromEvent, merge } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { NzDestroyService } from 'ng-zorro-antd/core/services'; + import { NzResizableService } from './resizable.service'; export type NzResizeDirection = @@ -47,41 +49,42 @@ export class NzResizeHandleMouseDownEvent { '[class.nz-resizable-handle-topRight]': `nzDirection === 'topRight'`, '[class.nz-resizable-handle-bottomRight]': `nzDirection === 'bottomRight'`, '[class.nz-resizable-handle-bottomLeft]': `nzDirection === 'bottomLeft'`, - '[class.nz-resizable-handle-topLeft]': `nzDirection === 'topLeft'`, - '(mousedown)': 'onMousedown($event)', - '(touchstart)': 'onMousedown($event)' - } + '[class.nz-resizable-handle-topLeft]': `nzDirection === 'topLeft'` + }, + providers: [NzDestroyService] }) -export class NzResizeHandleComponent implements OnInit, OnDestroy { +export class NzResizeHandleComponent implements OnInit { @Input() nzDirection: NzResizeDirection = 'bottomRight'; @Output() readonly nzMouseDown = new EventEmitter(); - private destroy$ = new Subject(); - constructor( + private ngZone: NgZone, private nzResizableService: NzResizableService, private renderer: Renderer2, - private elementRef: ElementRef + private host: ElementRef, + private destroy$: NzDestroyService ) {} ngOnInit(): void { - // Caretaker note: `mouseEntered$` subject will emit events within the `` zone, - // see `NzResizableDirective#ngAfterViewInit`. There're event listeners are added within the `` zone. - this.nzResizableService.mouseEntered$.pipe(takeUntil(this.destroy$)).subscribe(entered => { + this.nzResizableService.mouseEnteredOutsideAngular$.pipe(takeUntil(this.destroy$)).subscribe(entered => { if (entered) { - this.renderer.addClass(this.elementRef.nativeElement, 'nz-resizable-handle-box-hover'); + this.renderer.addClass(this.host.nativeElement, 'nz-resizable-handle-box-hover'); } else { - this.renderer.removeClass(this.elementRef.nativeElement, 'nz-resizable-handle-box-hover'); + this.renderer.removeClass(this.host.nativeElement, 'nz-resizable-handle-box-hover'); } }); - } - onMousedown(event: MouseEvent | TouchEvent): void { - this.nzResizableService.handleMouseDown$.next(new NzResizeHandleMouseDownEvent(this.nzDirection, event)); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); + this.ngZone.runOutsideAngular(() => { + merge( + fromEvent(this.host.nativeElement, 'mousedown'), + fromEvent(this.host.nativeElement, 'touchstart') + ) + .pipe(takeUntil(this.destroy$)) + .subscribe((event: MouseEvent | TouchEvent) => { + this.nzResizableService.handleMouseDownOutsideAngular$.next( + new NzResizeHandleMouseDownEvent(this.nzDirection, event) + ); + }); + }); } } diff --git a/components/resizable/resize-handles.component.ts b/components/resizable/resize-handles.component.ts index 237bcfdbe4..6f67076e10 100644 --- a/components/resizable/resize-handles.component.ts +++ b/components/resizable/resize-handles.component.ts @@ -26,11 +26,7 @@ export const DEFAULT_RESIZE_DIRECTION: NzResizeDirection[] = [ }) export class NzResizeHandlesComponent implements OnChanges { @Input() nzDirections: NzResizeDirection[] = DEFAULT_RESIZE_DIRECTION; - directions: Set; - - constructor() { - this.directions = new Set(this.nzDirections); - } + directions = new Set(this.nzDirections); ngOnChanges(changes: SimpleChanges): void { if (changes.nzDirections) {