Skip to content

Commit 6bab5ca

Browse files
authored
refactor(module:message,notification): use native animation (#9609)
1 parent a2ee405 commit 6bab5ca

13 files changed

Lines changed: 359 additions & 409 deletions

File tree

components/core/animation/move.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

components/core/animation/notification.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

components/core/animation/public-api.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
export * from './animation-consts';
77
export * from './collapse';
8-
export * from './move';
9-
export * from './notification';
108
export * from './slide';
119
export * from './zoom';
1210
export * from './no-animation';

components/message/base.ts

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,25 @@
33
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
44
*/
55

6-
import { AnimationEvent } from '@angular/animations';
76
import {
87
ComponentType,
98
createGlobalPositionStrategy,
109
createNoopScrollStrategy,
1110
createOverlayRef
1211
} from '@angular/cdk/overlay';
1312
import { ComponentPortal } from '@angular/cdk/portal';
14-
import { ChangeDetectorRef, DestroyRef, Directive, EventEmitter, inject, Injector, OnInit } from '@angular/core';
13+
import {
14+
ChangeDetectorRef,
15+
DestroyRef,
16+
Directive,
17+
ElementRef,
18+
EventEmitter,
19+
inject,
20+
Injector,
21+
OnInit,
22+
Signal
23+
} from '@angular/core';
1524
import { Subject } from 'rxjs';
16-
import { filter, take } from 'rxjs/operators';
1725

1826
import { MessageConfig, NzConfigService } from 'ng-zorro-antd/core/config';
1927
import { NzSingletonService } from 'ng-zorro-antd/core/services';
@@ -163,13 +171,27 @@ export abstract class NzMNComponent implements OnInit {
163171
abstract index?: number;
164172
abstract destroyed: EventEmitter<{ id: string; userAction: boolean }>;
165173

166-
protected cdr = inject(ChangeDetectorRef);
167174
protected destroyRef = inject(DestroyRef);
168-
readonly animationStateChanged: Subject<AnimationEvent> = new Subject<AnimationEvent>();
175+
protected elementRef = inject(ElementRef);
176+
177+
/**
178+
* To get the element which triggers the animation.
179+
*/
180+
protected abstract animationElement: Signal<ElementRef<HTMLElement>>;
181+
182+
/**
183+
* When the animation is enabled, it is used to judge whether the leave animation is done or not.
184+
* @note Keyframe of notification may have different keyframe names with individual placements, so use array here.
185+
*/
186+
protected abstract _animationKeyframeMap: Record<'enter' | 'leave', string | string[]>;
187+
188+
/**
189+
* The animation class map for enter and leave.
190+
*/
191+
protected abstract _animationClassMap: Record<'enter' | 'leave', string>;
169192

170193
protected options!: Required<NzMessageDataOptions>;
171194
protected autoClose?: boolean;
172-
protected closeTimer?: ReturnType<typeof setTimeout>;
173195
protected userAction: boolean = false;
174196
protected eraseTimer?: ReturnType<typeof setTimeout>;
175197
protected eraseTimingStart?: number;
@@ -180,24 +202,16 @@ export abstract class NzMNComponent implements OnInit {
180202
if (this.autoClose) {
181203
this.clearEraseTimeout();
182204
}
183-
this.animationStateChanged.complete();
184205
});
185206
}
186207

187208
ngOnInit(): void {
188209
this.options = this.instance.options as Required<NzMessageDataOptions>;
189210

190211
if (this.options.nzAnimate) {
212+
// todo: remove this line in v22.0.0
191213
this.instance.state = 'enter';
192-
this.animationStateChanged
193-
.pipe(
194-
filter(event => event.phaseName === 'done' && event.toState === 'leave'),
195-
take(1)
196-
)
197-
.subscribe(() => {
198-
clearTimeout(this.closeTimer);
199-
this.destroyed.next({ id: this.instance.messageId, userAction: this.userAction });
200-
});
214+
this._startEnterAnimation();
201215
}
202216

203217
this.autoClose = this.options.nzDuration > 0;
@@ -224,12 +238,9 @@ export abstract class NzMNComponent implements OnInit {
224238
protected destroy(userAction: boolean = false): void {
225239
this.userAction = userAction;
226240
if (this.options.nzAnimate) {
241+
// todo: remove this line in v22.0.0
227242
this.instance.state = 'leave';
228-
this.cdr.detectChanges();
229-
this.closeTimer = setTimeout(() => {
230-
this.closeTimer = undefined;
231-
this.destroyed.next({ id: this.instance.messageId, userAction });
232-
}, 200);
243+
this._startLeaveAnimation(() => this.destroyed.next({ id: this.instance.messageId, userAction }));
233244
} else {
234245
this.destroyed.next({ id: this.instance.messageId, userAction });
235246
}
@@ -262,4 +273,40 @@ export abstract class NzMNComponent implements OnInit {
262273
this.eraseTimer = undefined;
263274
}
264275
}
276+
277+
private _startEnterAnimation(): void {
278+
const element = this.animationElement().nativeElement;
279+
element.classList.add(this._animationClassMap.enter);
280+
281+
const onAnimationEnd = (event: AnimationEvent): void => {
282+
if (this.matchAnimationKeyframe(event, this._animationKeyframeMap.enter)) {
283+
element.removeEventListener('animationend', onAnimationEnd);
284+
// should remove animation enter class once animation is done
285+
element.classList.remove(this._animationClassMap.enter);
286+
}
287+
};
288+
289+
element.addEventListener('animationend', onAnimationEnd);
290+
}
291+
292+
private _startLeaveAnimation(callback: () => void): void {
293+
const element = this.animationElement().nativeElement;
294+
element.classList.remove(this._animationClassMap.enter);
295+
element.classList.add(this._animationClassMap.leave);
296+
297+
const onAnimationEnd = (event: AnimationEvent): void => {
298+
if (this.matchAnimationKeyframe(event, this._animationKeyframeMap.leave)) {
299+
element.removeEventListener('animationend', onAnimationEnd);
300+
callback();
301+
}
302+
};
303+
304+
element.addEventListener('animationend', onAnimationEnd);
305+
}
306+
307+
private matchAnimationKeyframe(event: AnimationEvent, animationName: string | string[]): boolean {
308+
return typeof animationName === 'string'
309+
? event.animationName === animationName
310+
: animationName.includes(event.animationName);
311+
}
265312
}

components/message/message.component.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,31 @@
66
import {
77
ChangeDetectionStrategy,
88
Component,
9+
ElementRef,
910
EventEmitter,
1011
Input,
1112
OnInit,
1213
Output,
14+
viewChild,
1315
ViewEncapsulation
1416
} from '@angular/core';
1517

16-
import { moveUpMotion } from 'ng-zorro-antd/core/animation';
1718
import { NzOutletModule } from 'ng-zorro-antd/core/outlet';
1819
import { NzIconModule } from 'ng-zorro-antd/icon';
1920

2021
import { NzMNComponent } from './base';
2122
import { NzMessageData } from './typings';
2223

2324
@Component({
24-
changeDetection: ChangeDetectionStrategy.OnPush,
25-
encapsulation: ViewEncapsulation.None,
2625
selector: 'nz-message',
2726
exportAs: 'nzMessage',
28-
animations: [moveUpMotion],
27+
imports: [NzIconModule, NzOutletModule],
2928
template: `
3029
<div
30+
#animationElement
3131
class="ant-message-notice"
3232
[class]="instance.options?.nzClass"
3333
[style]="instance.options?.nzStyle"
34-
[@moveUpMotion]="instance.state"
35-
(@moveUpMotion.done)="animationStateChanged.next($event)"
3634
(mouseenter)="onEnter()"
3735
(mouseleave)="onLeave()"
3836
>
@@ -64,10 +62,21 @@ import { NzMessageData } from './typings';
6462
</div>
6563
</div>
6664
`,
67-
imports: [NzIconModule, NzOutletModule]
65+
changeDetection: ChangeDetectionStrategy.OnPush,
66+
encapsulation: ViewEncapsulation.None
6867
})
6968
export class NzMessageComponent extends NzMNComponent implements OnInit {
7069
@Input() override instance!: Required<NzMessageData>;
7170
@Output() override readonly destroyed = new EventEmitter<{ id: string; userAction: boolean }>();
7271
index?: number;
72+
73+
readonly animationElement = viewChild.required('animationElement', { read: ElementRef });
74+
protected readonly _animationKeyframeMap = {
75+
enter: 'MessageMoveIn',
76+
leave: 'MessageMoveOut'
77+
};
78+
protected readonly _animationClassMap = {
79+
enter: 'ant-message-move-up-enter',
80+
leave: 'ant-message-move-up-leave'
81+
};
7382
}

0 commit comments

Comments
 (0)