license import { AnimationEvent } from '@angular/animations' ; import { BasePortalOutlet , CdkPortalOutlet , ComponentPortal , TemplatePortal , } from '@angular/cdk/portal' ; import { ChangeDetectionStrategy , ChangeDetectorRef , Component , ComponentRef , ElementRef , EmbeddedViewRef , NgZone , OnDestroy , ViewChild , ViewEncapsulation , } from '@angular/core' ; import { Observable , Subject } from 'rxjs' ; import { take } from 'rxjs/operators' ; import { matSnackBarAnimations } from './snack-bar-animations' ; import { MatSnackBarConfig } from './snack-bar-config' ; docs-private @ Component ( { moduleId : module . id , selector : 'snack-bar-container' , templateUrl : 'snack-bar-container.html' , styleUrls : [ 'snack-bar-container.css' ] , changeDetection : ChangeDetectionStrategy . OnPush , encapsulation : ViewEncapsulation . None , animations : [ matSnackBarAnimations . snackBarState ] , host : { '[attr.role]' : '_role' , 'class' : 'mat-snack-bar-container' , '[@state]' : '_animationState' , '(@state.done)' : 'onAnimationEnd($event)' } , } ) export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy { /** Whether the component has been destroyed. */ private _destroyed = false ; /** The portal outlet inside of this container into which the snack bar content will be loaded. */ @ ViewChild ( CdkPortalOutlet ) _portalOutlet : CdkPortalOutlet ; /** Subject for notifying that the snack bar has exited from view. */ readonly _onExit : Subject < any > = new Subject ( ) ; /** Subject for notifying that the snack bar has finished entering the view. */ readonly _onEnter : Subject < any > = new Subject ( ) ; /** The state of the snack bar animations. */ _animationState = 'void' ; /** ARIA role for the snack bar container. */ _role : 'alert' | 'status' | null ; constructor ( private _ngZone , private _elementRef , private _changeDetectorRef , /** The snack bar configuration. */ public snackBarConfig ) { super ( ) ; if ( snackBarConfig . politeness === 'assertive' && ! snackBarConfig . announcementMessage ) { this . _role = 'alert' ; } else if ( snackBarConfig . politeness === 'off' ) { this . _role = null ; } else { this . _role = 'status' ; } } /** Attach a component portal as content to this snack bar container. */ attachComponentPortal < T > ( portal ) { this . _assertNotAttached ( ) ; this . _applySnackBarClasses ( ) ; return this . _portalOutlet . attachComponentPortal ( portal ) ; } /** Attach a template portal as content to this snack bar container. */ attachTemplatePortal < C > ( portal ) { this . _assertNotAttached ( ) ; this . _applySnackBarClasses ( ) ; return this . _portalOutlet . attachTemplatePortal ( portal ) ; } /** Handle end of animations, updating the state of the snackbar. */ onAnimationEnd ( event ) { const { fromState , toState } = event ; if ( ( toState === 'void' && fromState !== 'void' ) || toState === 'hidden' ) { this . _completeExit ( ) ; } if ( toState === 'visible' ) { const onEnter = this . _onEnter ; this . _ngZone . run ( ( ) => { onEnter . next ( ) ; onEnter . complete ( ) ; } ) ; } } /** Begin animation of snack bar entrance into view. */ enter ( ) { if ( ! this . _destroyed ) { this . _animationState = 'visible' ; this . _changeDetectorRef . detectChanges ( ) ; } } /** Begin animation of the snack bar exiting from view. */ exit ( ) { this . _animationState = 'hidden' ; return this . _onExit ; } /** Makes sure the exit callbacks have been invoked when the element is destroyed. */ ngOnDestroy ( ) { this . _destroyed = true ; this . _completeExit ( ) ; } /**
     * Waits for the zone to settle before removing the element. Helps prevent
     * errors where we end up removing an element which is in the middle of an animation.
     */ private _completeExit ( ) { this . _ngZone . onMicrotaskEmpty . asObservable ( ) . pipe ( take ( 1 ) ) . subscribe ( ( ) => { this . _onExit . next ( ) ; this . _onExit . complete ( ) ; } ) ; } /** Applies the various positioning and user-configured CSS classes to the snack bar. */ private _applySnackBarClasses ( ) { const element = this . _elementRef . nativeElement ; const panelClasses = this . snackBarConfig . panelClass ; if ( panelClasses ) { if ( Array . isArray ( panelClasses ) ) { panelClasses . forEach ( cssClass => element . classList . add ( cssClass ) ) ; } else { element . classList . add ( panelClasses ) ; } } if ( this . snackBarConfig . horizontalPosition === 'center' ) { element . classList . add ( 'mat-snack-bar-center' ) ; } if ( this . snackBarConfig . verticalPosition === 'top' ) { element . classList . add ( 'mat-snack-bar-top' ) ; } } /** Asserts that no content is already attached to the container. */ private _assertNotAttached ( ) { if ( this . _portalOutlet . hasAttached ( ) ) { throw Error ( 'Attempting to attach snack bar content after content is already attached' ) ; } } }