Skip to content

Commit

Permalink
perf(module:resizable): do not run change detection on mousedown an…
Browse files Browse the repository at this point in the history
…d `touchstart` events (#7170)
  • Loading branch information
arturovt committed Feb 22, 2022
1 parent f0f52a4 commit 9a8d794
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 53 deletions.
29 changes: 14 additions & 15 deletions components/resizable/resizable.directive.ts
Expand Up @@ -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';

Expand All @@ -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',
Expand Down Expand Up @@ -68,38 +69,38 @@ export class NzResizableDirective implements AfterViewInit, OnDestroy {
private ghostElement: HTMLDivElement | null = null;
private el!: HTMLElement;
private sizeCache: NzResizeEvent | null = null;
private destroy$ = new Subject<void>();

constructor(
private elementRef: ElementRef<HTMLElement>,
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;
}
this.resizing = true;
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);
}
Expand Down Expand Up @@ -312,21 +313,19 @@ 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);
});
});
}

ngOnDestroy(): void {
this.ghostElement = null;
this.sizeCache = null;
this.destroy$.next();
this.destroy$.complete();
}
}
30 changes: 20 additions & 10 deletions components/resizable/resizable.service.ts
Expand Up @@ -17,10 +17,20 @@ export class NzResizableService implements OnDestroy {
private document: Document;
private listeners = new Map<string, (event: MouseEvent | TouchEvent) => void>();

handleMouseDown$ = new Subject<NzResizeHandleMouseDownEvent>();
documentMouseUp$ = new Subject<MouseEvent | TouchEvent>();
documentMouseMove$ = new Subject<MouseEvent | TouchEvent>();
mouseEntered$ = new Subject<boolean>();
/**
* 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); // <root>
* console.log(NgZone.isInAngularZone()); // false
* });
* ```
*/
handleMouseDownOutsideAngular$ = new Subject<NzResizeHandleMouseDownEvent>();
documentMouseUpOutsideAngular$ = new Subject<MouseEvent | TouchEvent>();
documentMouseMoveOutsideAngular$ = new Subject<MouseEvent | TouchEvent>();
mouseEnteredOutsideAngular$ = new Subject<boolean>();

constructor(private ngZone: NgZone, @Inject(DOCUMENT) document: NzSafeAny) {
this.document = document;
Expand All @@ -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();
};

Expand All @@ -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();
}
}
8 changes: 8 additions & 0 deletions components/resizable/resizable.spec.ts
Expand Up @@ -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;
Expand Down
49 changes: 26 additions & 23 deletions components/resizable/resize-handle.component.ts
Expand Up @@ -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 =
Expand Down Expand Up @@ -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<NzResizeHandleMouseDownEvent>();

private destroy$ = new Subject<void>();

constructor(
private ngZone: NgZone,
private nzResizableService: NzResizableService,
private renderer: Renderer2,
private elementRef: ElementRef
private host: ElementRef<HTMLElement>,
private destroy$: NzDestroyService
) {}

ngOnInit(): void {
// Caretaker note: `mouseEntered$` subject will emit events within the `<root>` zone,
// see `NzResizableDirective#ngAfterViewInit`. There're event listeners are added within the `<root>` 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<MouseEvent>(this.host.nativeElement, 'mousedown'),
fromEvent<TouchEvent>(this.host.nativeElement, 'touchstart')
)
.pipe(takeUntil(this.destroy$))
.subscribe((event: MouseEvent | TouchEvent) => {
this.nzResizableService.handleMouseDownOutsideAngular$.next(
new NzResizeHandleMouseDownEvent(this.nzDirection, event)
);
});
});
}
}
6 changes: 1 addition & 5 deletions components/resizable/resize-handles.component.ts
Expand Up @@ -26,11 +26,7 @@ export const DEFAULT_RESIZE_DIRECTION: NzResizeDirection[] = [
})
export class NzResizeHandlesComponent implements OnChanges {
@Input() nzDirections: NzResizeDirection[] = DEFAULT_RESIZE_DIRECTION;
directions: Set<NzResizeDirection>;

constructor() {
this.directions = new Set(this.nzDirections);
}
directions = new Set<NzResizeDirection>(this.nzDirections);

ngOnChanges(changes: SimpleChanges): void {
if (changes.nzDirections) {
Expand Down

0 comments on commit 9a8d794

Please sign in to comment.