Skip to content

Commit 7d091bb

Browse files
authored
perf(module:back-top): do not run change detection if there are no nzClick listeners (#7179)
1 parent 0054f59 commit 7d091bb

2 files changed

Lines changed: 45 additions & 19 deletions

File tree

components/back-top/back-top.component.ts

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ChangeDetectionStrategy,
1111
ChangeDetectorRef,
1212
Component,
13+
ElementRef,
1314
EventEmitter,
1415
Inject,
1516
Input,
@@ -21,14 +22,15 @@ import {
2122
Output,
2223
SimpleChanges,
2324
TemplateRef,
25+
ViewChild,
2426
ViewEncapsulation
2527
} from '@angular/core';
26-
import { fromEvent, Subject } from 'rxjs';
28+
import { fromEvent, Subject, Subscription } from 'rxjs';
2729
import { debounceTime, takeUntil } from 'rxjs/operators';
2830

2931
import { fadeMotion } from 'ng-zorro-antd/core/animation';
3032
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
31-
import { NzScrollService } from 'ng-zorro-antd/core/services';
33+
import { NzDestroyService, NzScrollService } from 'ng-zorro-antd/core/services';
3234
import { NumberInput, NzSafeAny } from 'ng-zorro-antd/core/types';
3335
import { InputNumber } from 'ng-zorro-antd/core/util';
3436

@@ -39,13 +41,7 @@ const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'backTop';
3941
exportAs: 'nzBackTop',
4042
animations: [fadeMotion],
4143
template: `
42-
<div
43-
class="ant-back-top"
44-
[class.ant-back-top-rtl]="dir === 'rtl'"
45-
(click)="clickBackTop()"
46-
@fadeMotion
47-
*ngIf="visible"
48-
>
44+
<div #backTop class="ant-back-top" [class.ant-back-top-rtl]="dir === 'rtl'" @fadeMotion *ngIf="visible">
4945
<ng-template #defaultContent>
5046
<div class="ant-back-top-content">
5147
<div class="ant-back-top-icon">
@@ -58,15 +54,15 @@ const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'backTop';
5854
`,
5955
changeDetection: ChangeDetectionStrategy.OnPush,
6056
encapsulation: ViewEncapsulation.None,
61-
preserveWhitespaces: false
57+
preserveWhitespaces: false,
58+
providers: [NzDestroyService]
6259
})
6360
export class NzBackTopComponent implements OnInit, OnDestroy, OnChanges {
6461
readonly _nzModuleName: NzConfigKey = NZ_CONFIG_MODULE_NAME;
6562
static ngAcceptInputType_nzVisibilityHeight: NumberInput;
6663
static ngAcceptInputType_nzDuration: NumberInput;
6764

6865
private scrollListenerDestroy$ = new Subject();
69-
private destroy$ = new Subject();
7066
private target: HTMLElement | null = null;
7167

7268
visible: boolean = false;
@@ -78,6 +74,26 @@ export class NzBackTopComponent implements OnInit, OnDestroy, OnChanges {
7874
@Input() @InputNumber() nzDuration: number = 450;
7975
@Output() readonly nzClick: EventEmitter<boolean> = new EventEmitter();
8076

77+
@ViewChild('backTop', { static: false })
78+
set backTop(backTop: ElementRef<HTMLElement> | undefined) {
79+
if (backTop) {
80+
this.backTopClickSubscription.unsubscribe();
81+
82+
this.backTopClickSubscription = this.zone.runOutsideAngular(() =>
83+
fromEvent(backTop.nativeElement, 'click')
84+
.pipe(takeUntil(this.destroy$))
85+
.subscribe(() => {
86+
this.scrollSrv.scrollTo(this.getTarget(), 0, { duration: this.nzDuration });
87+
if (this.nzClick.observers.length) {
88+
this.zone.run(() => this.nzClick.emit(true));
89+
}
90+
})
91+
);
92+
}
93+
}
94+
95+
private backTopClickSubscription = Subscription.EMPTY;
96+
8197
constructor(
8298
@Inject(DOCUMENT) private doc: NzSafeAny,
8399
public nzConfigService: NzConfigService,
@@ -86,6 +102,7 @@ export class NzBackTopComponent implements OnInit, OnDestroy, OnChanges {
86102
private cd: ChangeDetectorRef,
87103
private zone: NgZone,
88104
private cdr: ChangeDetectorRef,
105+
private destroy$: NzDestroyService,
89106
@Optional() private directionality: Directionality
90107
) {
91108
this.dir = this.directionality.value;
@@ -102,11 +119,6 @@ export class NzBackTopComponent implements OnInit, OnDestroy, OnChanges {
102119
this.dir = this.directionality.value;
103120
}
104121

105-
clickBackTop(): void {
106-
this.scrollSrv.scrollTo(this.getTarget(), 0, { duration: this.nzDuration });
107-
this.nzClick.emit(true);
108-
}
109-
110122
private getTarget(): HTMLElement | Window {
111123
return this.target || window;
112124
}
@@ -135,8 +147,6 @@ export class NzBackTopComponent implements OnInit, OnDestroy, OnChanges {
135147
ngOnDestroy(): void {
136148
this.scrollListenerDestroy$.next();
137149
this.scrollListenerDestroy$.complete();
138-
this.destroy$.next();
139-
this.destroy$.complete();
140150
}
141151

142152
ngOnChanges(changes: SimpleChanges): void {

components/back-top/back-top.spec.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, DebugElement, ViewChild } from '@angular/core';
1+
import { ApplicationRef, Component, DebugElement, ViewChild } from '@angular/core';
22
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
33
import { By } from '@angular/platform-browser';
44
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -143,6 +143,22 @@ describe('Component:nz-back-top', () => {
143143
componentObject.clickBackTop();
144144
}));
145145
});
146+
147+
describe('change detection behavior', () => {
148+
it('should not run change detection if there are no `nzClick` listeners', () => {
149+
const appRef = TestBed.inject(ApplicationRef);
150+
spyOn(appRef, 'tick');
151+
152+
const backTopButton = componentObject.backTopButton().nativeElement;
153+
backTopButton.dispatchEvent(new MouseEvent('click'));
154+
expect(appRef.tick).not.toHaveBeenCalled();
155+
156+
component.nzClick.subscribe();
157+
158+
backTopButton.dispatchEvent(new MouseEvent('click'));
159+
expect(appRef.tick).toHaveBeenCalled();
160+
});
161+
});
146162
});
147163

148164
describe('[nzTarget]', () => {

0 commit comments

Comments
 (0)