3
3
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
4
4
*/
5
5
6
+ import { isPlatformBrowser } from '@angular/common' ;
6
7
import {
7
8
AfterContentChecked ,
8
9
ChangeDetectorRef ,
@@ -11,16 +12,19 @@ import {
11
12
Input ,
12
13
NgZone ,
13
14
OnChanges ,
14
- OnDestroy ,
15
15
OnInit ,
16
16
Renderer2 ,
17
17
SimpleChanges ,
18
18
booleanAttribute ,
19
+ numberAttribute ,
20
+ ExperimentalPendingTasks ,
19
21
inject ,
20
- numberAttribute
22
+ DestroyRef ,
23
+ PLATFORM_ID
21
24
} from '@angular/core' ;
22
- import { Subject , from } from 'rxjs' ;
23
- import { takeUntil } from 'rxjs/operators' ;
25
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop' ;
26
+ import { animationFrameScheduler , asapScheduler , from } from 'rxjs' ;
27
+ import { debounceTime , finalize } from 'rxjs/operators' ;
24
28
25
29
import { IconDirective , ThemeType } from '@ant-design/icons-angular' ;
26
30
@@ -36,7 +40,7 @@ import { NzIconPatchService, NzIconService } from './icon.service';
36
40
} ,
37
41
standalone : true
38
42
} )
39
- export class NzIconDirective extends IconDirective implements OnInit , OnChanges , AfterContentChecked , OnDestroy {
43
+ export class NzIconDirective extends IconDirective implements OnInit , OnChanges , AfterContentChecked {
40
44
cacheClassName : string | null = null ;
41
45
@Input ( { transform : booleanAttribute } )
42
46
set nzSpin ( value : boolean ) {
@@ -71,7 +75,9 @@ export class NzIconDirective extends IconDirective implements OnInit, OnChanges,
71
75
private iconfont ?: string ;
72
76
private spin : boolean = false ;
73
77
74
- private destroy$ = new Subject < void > ( ) ;
78
+ private destroyRef = inject ( DestroyRef ) ;
79
+ private pendingTasks = inject ( ExperimentalPendingTasks ) ;
80
+ private isBrowser = isPlatformBrowser ( inject ( PLATFORM_ID ) ) ;
75
81
76
82
constructor (
77
83
private readonly ngZone : NgZone ,
@@ -94,7 +100,9 @@ export class NzIconDirective extends IconDirective implements OnInit, OnChanges,
94
100
const { nzType, nzTwotoneColor, nzSpin, nzTheme, nzRotate } = changes ;
95
101
96
102
if ( nzType || nzTwotoneColor || nzSpin || nzTheme ) {
97
- this . changeIcon2 ( ) ;
103
+ // This is used to reduce the number of change detections
104
+ // while the icon is being loaded asynchronously.
105
+ this . ngZone . runOutsideAngular ( ( ) => this . changeIcon2 ( ) ) ;
98
106
} else if ( nzRotate ) {
99
107
this . handleRotate ( this . el . firstChild as SVGElement ) ;
100
108
} else {
@@ -124,46 +132,47 @@ export class NzIconDirective extends IconDirective implements OnInit, OnChanges,
124
132
}
125
133
}
126
134
127
- ngOnDestroy ( ) : void {
128
- this . destroy$ . next ( ) ;
129
- }
130
-
131
135
/**
132
136
* Replacement of `changeIcon` for more modifications.
133
137
*/
134
138
private changeIcon2 ( ) : void {
135
139
this . setClassName ( ) ;
136
140
137
- // The Angular zone is left deliberately before the SVG is set
138
- // since `_changeIcon` spawns asynchronous tasks as promise and
139
- // HTTP calls. This is used to reduce the number of change detections
140
- // while the icon is being loaded dynamically.
141
- this . ngZone . runOutsideAngular ( ( ) => {
142
- from ( this . _changeIcon ( ) )
143
- . pipe ( takeUntil ( this . destroy$ ) )
144
- . subscribe ( {
145
- next : svgOrRemove => {
146
- // Get back into the Angular zone after completing all the tasks.
147
- // Since we manually run change detection locally, we have to re-enter
148
- // the zone because the change detection might also be run on other local
149
- // components, leading them to handle template functions outside of the Angular zone.
150
- this . ngZone . run ( ( ) => {
151
- // The _changeIcon method would call Renderer to remove the element of the old icon,
152
- // which would call `markElementAsRemoved` eventually,
153
- // so we should call `detectChanges` to tell Angular remove the DOM node.
154
- // #7186
155
- this . changeDetectorRef . detectChanges ( ) ;
156
-
157
- if ( svgOrRemove ) {
158
- this . setSVGData ( svgOrRemove ) ;
159
- this . handleSpin ( svgOrRemove ) ;
160
- this . handleRotate ( svgOrRemove ) ;
161
- }
162
- } ) ;
163
- } ,
164
- error : warn
165
- } ) ;
166
- } ) ;
141
+ // It is used to hydrate the icon component property when
142
+ // zoneless change detection is used in conjunction with server-side rendering.
143
+ const removeTask = this . pendingTasks . add ( ) ;
144
+
145
+ from ( this . _changeIcon ( ) )
146
+ . pipe (
147
+ // We need to individually debounce the icon rendering on each animation
148
+ // frame to prevent frame drops when many icons are being rendered on the
149
+ // page, such as in a `@for` loop.
150
+ debounceTime ( 0 , this . isBrowser ? animationFrameScheduler : asapScheduler ) ,
151
+ takeUntilDestroyed ( this . destroyRef ) ,
152
+ finalize ( removeTask )
153
+ )
154
+ . subscribe ( {
155
+ next : svgOrRemove => {
156
+ // Get back into the Angular zone after completing all the tasks.
157
+ // Since we manually run change detection locally, we have to re-enter
158
+ // the zone because the change detection might also be run on other local
159
+ // components, leading them to handle template functions outside of the Angular zone.
160
+ this . ngZone . run ( ( ) => {
161
+ // The _changeIcon method would call Renderer to remove the element of the old icon,
162
+ // which would call `markElementAsRemoved` eventually,
163
+ // so we should call `detectChanges` to tell Angular remove the DOM node.
164
+ // #7186
165
+ this . changeDetectorRef . detectChanges ( ) ;
166
+
167
+ if ( svgOrRemove ) {
168
+ this . setSVGData ( svgOrRemove ) ;
169
+ this . handleSpin ( svgOrRemove ) ;
170
+ this . handleRotate ( svgOrRemove ) ;
171
+ }
172
+ } ) ;
173
+ } ,
174
+ error : warn
175
+ } ) ;
167
176
}
168
177
169
178
private handleSpin ( svg : SVGElement ) : void {
0 commit comments