@@ -35,7 +35,7 @@ import {
3535import { DOCUMENT } from '@angular/common' ;
3636import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations' ;
3737import { Subject } from 'rxjs' ;
38- import { filter , startWith , take } from 'rxjs/operators' ;
38+ import { filter , startWith , take , distinctUntilChanged } from 'rxjs/operators' ;
3939import { matExpansionAnimations } from './expansion-animations' ;
4040import { MatExpansionPanelContent } from './expansion-panel-content' ;
4141import { MAT_ACCORDION , MatAccordionBase } from './accordion-base' ;
@@ -119,6 +119,9 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI
119119 /** ID for the associated header element. Used for a11y labelling. */
120120 _headerId = `mat-expansion-panel-header-${ uniqueId ++ } ` ;
121121
122+ /** Stream of body animation done events. */
123+ _bodyAnimationDone = new Subject < AnimationEvent > ( ) ;
124+
122125 constructor ( @Optional ( ) @SkipSelf ( ) @Inject ( MAT_ACCORDION ) accordion : MatAccordionBase ,
123126 _changeDetectorRef : ChangeDetectorRef ,
124127 _uniqueSelectionDispatcher : UniqueSelectionDispatcher ,
@@ -129,6 +132,20 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI
129132 super ( accordion , _changeDetectorRef , _uniqueSelectionDispatcher ) ;
130133 this . accordion = accordion ;
131134 this . _document = _document ;
135+
136+ // We need a Subject with distinctUntilChanged, because the `done` event
137+ // fires twice on some browsers. See https://github.com/angular/angular/issues/24084
138+ this . _bodyAnimationDone . pipe ( distinctUntilChanged ( ( x , y ) => {
139+ return x . fromState === y . fromState && x . toState === y . toState ;
140+ } ) ) . subscribe ( event => {
141+ if ( event . fromState !== 'void' ) {
142+ if ( event . toState === 'expanded' ) {
143+ this . afterExpand . emit ( ) ;
144+ } else if ( event . toState === 'collapsed' ) {
145+ this . afterCollapse . emit ( ) ;
146+ }
147+ }
148+ } ) ;
132149 }
133150
134151 /** Determines whether the expansion panel should have spacing between it and its siblings. */
@@ -166,20 +183,10 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI
166183
167184 ngOnDestroy ( ) {
168185 super . ngOnDestroy ( ) ;
186+ this . _bodyAnimationDone . complete ( ) ;
169187 this . _inputChanges . complete ( ) ;
170188 }
171189
172- _bodyAnimation ( event : AnimationEvent ) {
173- const { phaseName, toState, fromState} = event ;
174-
175- if ( phaseName === 'done' && toState === 'expanded' && fromState !== 'void' ) {
176- this . afterExpand . emit ( ) ;
177- }
178- if ( phaseName === 'done' && toState === 'collapsed' && fromState !== 'void' ) {
179- this . afterCollapse . emit ( ) ;
180- }
181- }
182-
183190 /** Checks whether the expansion panel's content contains the currently-focused element. */
184191 _containsFocus ( ) : boolean {
185192 if ( this . _body && this . _document ) {
0 commit comments