Skip to content

Commit ba7d53d

Browse files
crisbetoandrewseguin
authored andcommitted
fix(experimental/dialog): emitting events twice on some browsers (#13587)
Fixes the `Dialog` emitting some of its events twice on certain browsers due to an issue in `@angular/animations` which invokes the animation `done` callback twice. Fixes #13585.
1 parent 3ee9b47 commit ba7d53d

File tree

1 file changed

+24
-14
lines changed

1 file changed

+24
-14
lines changed

src/cdk-experimental/dialog/dialog-container.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
ViewEncapsulation,
3131
} from '@angular/core';
3232
import {Subject} from 'rxjs';
33+
import {distinctUntilChanged} from 'rxjs/operators';
3334
import {DialogConfig} from './dialog-config';
3435

3536

@@ -61,7 +62,7 @@ export function throwDialogContentAlreadyAttachedError() {
6162
host: {
6263
'[@dialog]': '_state',
6364
'(@dialog.start)': '_onAnimationStart($event)',
64-
'(@dialog.done)': '_onAnimationDone($event)',
65+
'(@dialog.done)': '_animationDone.next($event)',
6566
},
6667
})
6768
export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
@@ -103,6 +104,9 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
103104
/** A subject emitting after the dialog exits the view. */
104105
_afterExit: Subject<void> = new Subject();
105106

107+
/** Stream of animation `done` events. */
108+
_animationDone = new Subject<AnimationEvent>();
109+
106110
constructor(
107111
private _elementRef: ElementRef<HTMLElement>,
108112
private _focusTrapFactory: FocusTrapFactory,
@@ -111,11 +115,30 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
111115
/** The dialog configuration. */
112116
public _config: DialogConfig) {
113117
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+
});
114136
}
115137

116138
/** Destroy focus trap to place focus back to the element focused before the dialog opened. */
117139
ngOnDestroy() {
118140
this._focusTrap.destroy();
141+
this._animationDone.complete();
119142
}
120143

121144
/**
@@ -154,19 +177,6 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
154177
}
155178
}
156179

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-
170180
/** Starts the dialog exit animation. */
171181
_startExiting(): void {
172182
this._state = 'exit';

0 commit comments

Comments
 (0)