Skip to content

Commit

Permalink
perf(module:transfer): do not trigger change detection when the check…
Browse files Browse the repository at this point in the history
…box is clicked (#7124)
  • Loading branch information
arturovt committed Jan 13, 2022
1 parent 15abe33 commit b12f43a
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
38 changes: 35 additions & 3 deletions components/transfer/transfer-list.component.ts
Expand Up @@ -4,15 +4,22 @@
*/

import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
NgZone,
Output,
QueryList,
TemplateRef,
ViewChildren,
ViewEncapsulation
} from '@angular/core';
import { fromEvent, merge, Observable } from 'rxjs';
import { startWith, switchMap } from 'rxjs/operators';

import { TransferDirection, TransferItem } from './interface';

Expand All @@ -30,10 +37,10 @@ import { TransferDirection, TransferItem } from './interface';
[ngClass]="{ 'ant-transfer-list-content-item-disabled': disabled || item.disabled }"
>
<label
#checkboxes
nz-checkbox
[nzChecked]="item.checked"
(nzCheckedChange)="onItemSelect(item)"
(click)="$event.stopPropagation()"
[nzDisabled]="disabled || item.disabled"
>
<ng-container *ngIf="!render; else renderContainer">{{ item.title }}</ng-container>
Expand Down Expand Up @@ -111,7 +118,7 @@ import { TransferDirection, TransferItem } from './interface';
'[class.ant-transfer-list-with-footer]': '!!footer'
}
})
export class NzTransferListComponent {
export class NzTransferListComponent implements AfterViewInit {
// #region fields

@Input() direction: TransferDirection = 'left';
Expand All @@ -138,6 +145,8 @@ export class NzTransferListComponent {
@Output() readonly handleSelect: EventEmitter<TransferItem> = new EventEmitter();
@Output() readonly filterChange: EventEmitter<{ direction: TransferDirection; value: string }> = new EventEmitter();

@ViewChildren('checkboxes', { read: ElementRef }) checkboxes!: QueryList<ElementRef<HTMLLabelElement>>;

stat = {
checkAll: false,
checkHalf: false,
Expand Down Expand Up @@ -203,10 +212,33 @@ export class NzTransferListComponent {

// #endregion

constructor(private cdr: ChangeDetectorRef) {}
constructor(private ngZone: NgZone, private cdr: ChangeDetectorRef) {}

markForCheck(): void {
this.updateCheckStatus();
this.cdr.markForCheck();
}

ngAfterViewInit(): void {
this.checkboxes.changes
.pipe(
startWith(this.checkboxes),
switchMap(() => {
const checkboxes = this.checkboxes.toArray();
// Caretaker note: we explicitly should call `subscribe()` within the root zone.
// `runOutsideAngular(() => fromEvent(...))` will just create an observable within the root zone,
// but `addEventListener` is called when the `fromEvent` is subscribed.
return new Observable<MouseEvent>(subscriber =>
this.ngZone.runOutsideAngular(() =>
merge(...checkboxes.map(checkbox => fromEvent<MouseEvent>(checkbox.nativeElement, 'click'))).subscribe(
subscriber
)
)
);
})
)
.subscribe(event => {
event.stopPropagation();
});
}
}
28 changes: 27 additions & 1 deletion components/transfer/transfer.spec.ts
@@ -1,6 +1,15 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { BidiModule, Dir } from '@angular/cdk/bidi';
import { Component, DebugElement, Injector, OnInit, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
import {
ApplicationRef,
Component,
DebugElement,
Injector,
OnInit,
TemplateRef,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
Expand Down Expand Up @@ -281,6 +290,23 @@ describe('transfer', () => {
).attributes.getNamedItem('placeholder')!.textContent;
expect(searchPhText).toBe(en_US.Transfer.searchPlaceholder);
});

describe('change detection behavior', () => {
it('should not trigger change detection when the `ant-transfer-list-content-item label` is clicked', () => {
const appRef = TestBed.inject(ApplicationRef);
const event = new MouseEvent('click');

spyOn(appRef, 'tick');
spyOn(event, 'stopPropagation').and.callThrough();

const [label] = fixture.nativeElement.querySelectorAll('.ant-transfer-list-content-item label');

label.dispatchEvent(event);

expect(appRef.tick).not.toHaveBeenCalled();
expect(event.stopPropagation).toHaveBeenCalled();
});
});
});

describe('#canMove', () => {
Expand Down

0 comments on commit b12f43a

Please sign in to comment.