Skip to content

Commit a22122e

Browse files
committed
feat(cdk): add animation-aware-dialog
1 parent e7dea21 commit a22122e

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { animateChild, query, transition, trigger } from '@angular/animations';
2+
import {
3+
CdkDialogContainer,
4+
Dialog,
5+
DialogConfig,
6+
DialogRef,
7+
} from '@angular/cdk/dialog';
8+
import {
9+
BasePortalOutlet,
10+
CdkPortalOutlet,
11+
ComponentType,
12+
} from '@angular/cdk/portal';
13+
import { Component, Injectable, TemplateRef } from '@angular/core';
14+
import { firstValueFrom, Subject } from 'rxjs';
15+
16+
/**
17+
* Currently, when closing a dialog, the corresponding overlay is immediately
18+
* disposed, which means that the host element of the overlay is immediately
19+
* removed from the DOM using the native API.
20+
*
21+
* The current behavior completely eliminated the possibility to implement a
22+
* `:leave` animation for dialogs, since the ancestor element of the dialog,
23+
* the overlay, is removed via the native DOM API, and the Angular animation
24+
* engine cannot hook into this process to perform the `:leave` animation.
25+
*
26+
* This class works around the issue by waiting `:leave` animations to complete
27+
* before disposing the overlay.
28+
*
29+
* @see https://github.com/angular/components/issues/28878
30+
*/
31+
@Injectable()
32+
export class AnimationAwareDialog extends Dialog {
33+
override open<R = unknown, D = unknown, C = unknown>(
34+
componentOrTemplateRef: ComponentType<C> | TemplateRef<C>,
35+
config: DialogConfig<D, DialogRef<R, C>, BasePortalOutlet> = {},
36+
): DialogRef<R, C> {
37+
if (componentOrTemplateRef instanceof TemplateRef)
38+
throw new Error('TemplateRef not supported');
39+
config.container = AnimationAwareDialogContainer;
40+
const ref = super.open(componentOrTemplateRef, config);
41+
const close = ref.close.bind(ref);
42+
ref.close = async (...args) => {
43+
const container = ref.containerInstance as AnimationAwareDialogContainer;
44+
const animationDone = firstValueFrom(container.animationDone$);
45+
// Detaching an overlay removes the overlay's content through standard
46+
// Angular APIs, so the animations can be correctly triggered.
47+
ref.overlayRef.detach();
48+
await animationDone;
49+
close(...args);
50+
};
51+
return ref;
52+
}
53+
}
54+
55+
@Component({
56+
selector: 'agl-animation-aware-dialog-container',
57+
standalone: true,
58+
imports: [CdkPortalOutlet],
59+
template: `<ng-template cdkPortalOutlet />`,
60+
styles: `
61+
:host {
62+
display: block;
63+
}
64+
`,
65+
host: {
66+
'[@DialogContainer]': '',
67+
'(@DialogContainer.done)': 'animationDone$.next()',
68+
},
69+
animations: [
70+
trigger('DialogContainer', [
71+
transition(':leave', query('@*', animateChild(), { optional: true })),
72+
]),
73+
],
74+
})
75+
export class AnimationAwareDialogContainer extends CdkDialogContainer {
76+
animationDone$ = new Subject<void>();
77+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export * from './dialog';
1+
export * from './animation-aware-dialog';
2+
export * from './dialog-facade';

0 commit comments

Comments
 (0)