@@ -30,6 +30,7 @@ import {
30
30
ViewEncapsulation ,
31
31
} from '@angular/core' ;
32
32
import { Subject } from 'rxjs' ;
33
+ import { distinctUntilChanged } from 'rxjs/operators' ;
33
34
import { DialogConfig } from './dialog-config' ;
34
35
35
36
@@ -61,7 +62,7 @@ export function throwDialogContentAlreadyAttachedError() {
61
62
host : {
62
63
'[@dialog]' : '_state' ,
63
64
'(@dialog.start)' : '_onAnimationStart($event)' ,
64
- '(@dialog.done)' : '_onAnimationDone ($event)' ,
65
+ '(@dialog.done)' : '_animationDone.next ($event)' ,
65
66
} ,
66
67
} )
67
68
export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
@@ -103,6 +104,9 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
103
104
/** A subject emitting after the dialog exits the view. */
104
105
_afterExit : Subject < void > = new Subject ( ) ;
105
106
107
+ /** Stream of animation `done` events. */
108
+ _animationDone = new Subject < AnimationEvent > ( ) ;
109
+
106
110
constructor (
107
111
private _elementRef : ElementRef < HTMLElement > ,
108
112
private _focusTrapFactory : FocusTrapFactory ,
@@ -111,11 +115,30 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
111
115
/** The dialog configuration. */
112
116
public _config : DialogConfig ) {
113
117
super ( ) ;
118
+
119
+ // We use a Subject with a distinctUntilChanged, rather than a callback attached to .done,
120
+ // because some browsers fire the done event twice and we don't want to emit duplicate events.
121
+ // See: https://github.com/angular/angular/issues/24084
122
+ this . _animationDone . pipe ( distinctUntilChanged ( ( x , y ) => {
123
+ return x . fromState === y . fromState && x . toState === y . toState ;
124
+ } ) ) . subscribe ( event => {
125
+ // Emit lifecycle events based on animation `done` callback.
126
+ if ( event . toState === 'enter' ) {
127
+ this . _autoFocusFirstTabbableElement ( ) ;
128
+ this . _afterEnter . next ( ) ;
129
+ }
130
+
131
+ if ( event . fromState === 'enter' && ( event . toState === 'void' || event . toState === 'exit' ) ) {
132
+ this . _returnFocusAfterDialog ( ) ;
133
+ this . _afterExit . next ( ) ;
134
+ }
135
+ } ) ;
114
136
}
115
137
116
138
/** Destroy focus trap to place focus back to the element focused before the dialog opened. */
117
139
ngOnDestroy ( ) {
118
140
this . _focusTrap . destroy ( ) ;
141
+ this . _animationDone . complete ( ) ;
119
142
}
120
143
121
144
/**
@@ -154,19 +177,6 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
154
177
}
155
178
}
156
179
157
- /** Emit lifecycle events based on animation `done` callback. */
158
- _onAnimationDone ( event : AnimationEvent ) {
159
- if ( event . toState === 'enter' ) {
160
- this . _autoFocusFirstTabbableElement ( ) ;
161
- this . _afterEnter . next ( ) ;
162
- }
163
-
164
- if ( event . fromState === 'enter' && ( event . toState === 'void' || event . toState === 'exit' ) ) {
165
- this . _returnFocusAfterDialog ( ) ;
166
- this . _afterExit . next ( ) ;
167
- }
168
- }
169
-
170
180
/** Starts the dialog exit animation. */
171
181
_startExiting ( ) : void {
172
182
this . _state = 'exit' ;
0 commit comments