Skip to content

feat(snackbar): add timeout for snackbar #1856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/demo-app/snack-bar/snack-bar-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ <h1>SnackBar demo</h1>
[(ngModel)]="actionButtonLabel"></md-input>
</md-checkbox>
</div>
<div>
<md-checkbox [(ngModel)]="setAutoHide">
<p *ngIf="!setAutoHide">Auto hide after duration</p>
<md-input type="number" class="demo-button-label-input"
*ngIf="setAutoHide"
placeholder="Auto Hide Duration in ms"
[(ngModel)]="autoHide"></md-input>
</md-checkbox>
</div>
</div>

<button md-raised-button (click)="open()">OPEN</button>
8 changes: 6 additions & 2 deletions src/demo-app/snack-bar/snack-bar-demo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {MdSnackBar} from '@angular/material';
import {MdSnackBar, MdSnackBarConfig} from '@angular/material';

@Component({
moduleId: module.id,
Expand All @@ -10,11 +10,15 @@ export class SnackBarDemo {
message: string = 'Snack Bar opened.';
actionButtonLabel: string = 'Retry';
action: boolean = false;
setAutoHide: boolean = true;
autoHide: number = 0;

constructor(
public snackBar: MdSnackBar) { }

open() {
this.snackBar.open(this.message, this.action && this.actionButtonLabel);
let config = new MdSnackBarConfig();
config.duration = this.autoHide;
this.snackBar.open(this.message, this.action && this.actionButtonLabel, config);
}
}
1 change: 1 addition & 0 deletions src/lib/snack-bar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| `viewContainerRef: ViewContainerRef` | The view container ref to attach the snack bar to. |
| `role: AriaLivePoliteness = 'assertive'` | The politeness level to announce the snack bar with. |
| `announcementMessage: string` | The message to announce with the snack bar. |
| `duration: number` | The length of time in milliseconds to wait before autodismissing the snack bar. |


### Example
Expand Down
3 changes: 3 additions & 0 deletions src/lib/snack-bar/snack-bar-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ export class MdSnackBarConfig {

/** The view container to place the overlay for the snack bar into. */
viewContainerRef?: ViewContainerRef = null;

/** The length of time in milliseconds to wait before automatically dismissing the snack bar. */
duration?: number = 0;
}
28 changes: 21 additions & 7 deletions src/lib/snack-bar/snack-bar-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const HIDE_ANIMATION = '195ms cubic-bezier(0.0,0.0,0.2,1)';
host: {
'role': 'alert',
'[@state]': 'animationState',
'(@state.done)': 'markAsExited($event)'
'(@state.done)': 'onAnimationEnd($event)'
},
animations: [
trigger('state', [
Expand All @@ -58,7 +58,10 @@ export class MdSnackBarContainer extends BasePortalHost {
@ViewChild(PortalHostDirective) _portalHost: PortalHostDirective;

/** Subject for notifying that the snack bar has exited from view. */
private _onExit: Subject<any> = new Subject();
private onExit: Subject<any> = new Subject();

/** Subject for notifying that the snack bar has finished entering the view. */
private onEnter: Subject<any> = new Subject();

/** The state of the snack bar animations. */
animationState: SnackBarState = 'initial';
Expand Down Expand Up @@ -87,15 +90,21 @@ export class MdSnackBarContainer extends BasePortalHost {
/** Begin animation of the snack bar exiting from view. */
exit(): Observable<void> {
this.animationState = 'complete';
return this._onExit.asObservable();
return this.onExit.asObservable();
}

/** Mark snack bar as exited from the view. */
markAsExited(event: AnimationTransitionEvent) {
/** Handle end of animations, updating the state of the snackbar. */
onAnimationEnd(event: AnimationTransitionEvent) {
if (event.toState === 'void' || event.toState === 'complete') {
this._ngZone.run(() => {
this._onExit.next();
this._onExit.complete();
this.onExit.next();
this.onExit.complete();
});
}
if (event.toState === 'visible') {
this._ngZone.run(() => {
this.onEnter.next();
this.onEnter.complete();
});
}
}
Expand All @@ -104,4 +113,9 @@ export class MdSnackBarContainer extends BasePortalHost {
enter(): void {
this.animationState = 'visible';
}

/** Returns an observable resolving when the enter animation completes. */
_onEnter(): Observable<void> {
return this.onEnter.asObservable();
}
}
16 changes: 16 additions & 0 deletions src/lib/snack-bar/snack-bar-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export class MdSnackBarRef<T> {
/** Subject for notifying the user that the snack bar has closed. */
private _afterClosed: Subject<any> = new Subject();

/** Subject for notifying the user that the snack bar has opened and appeared. */
private _afterOpened: Subject<any>;

/** Subject for notifying the user that the snack bar action was called. */
private _onAction: Subject<any> = new Subject();

Expand Down Expand Up @@ -51,11 +54,24 @@ export class MdSnackBarRef<T> {
}
}

/** Marks the snackbar as opened */
_open(): void {
if (!this._afterOpened.closed) {
this._afterOpened.next();
this._afterOpened.complete();
}
}

/** Gets an observable that is notified when the snack bar is finished closing. */
afterDismissed(): Observable<void> {
return this._afterClosed.asObservable();
}

/** Gets an observable that is notified when the snack bar has opened and appeared. */
afterOpened(): Observable<void> {
return this.containerInstance._onEnter();
}

/** Gets an observable that is notified when the snack bar action is called. */
onAction(): Observable<void> {
return this._onAction.asObservable();
Expand Down
26 changes: 22 additions & 4 deletions src/lib/snack-bar/snack-bar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@angular/core/testing';
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
import {MdSnackBar, MdSnackBarModule} from './snack-bar';
import {MdSnackBarConfig} from './snack-bar-config';
import {OverlayContainer, MdLiveAnnouncer} from '../core';
import {SimpleSnackBar} from './simple-snack-bar';

Expand Down Expand Up @@ -280,13 +281,30 @@ describe('MdSnackBar', () => {
viewContainerFixture.detectChanges();
flushMicrotasks();

viewContainerFixture.whenStable().then(() => {
expect(dismissObservableCompleted).toBeTruthy('Expected the snack bar to be dismissed');
expect(actionObservableCompleted).toBeTruthy('Expected the snack bar to notify of action');
});
expect(dismissObservableCompleted).toBeTruthy('Expected the snack bar to be dismissed');
expect(actionObservableCompleted).toBeTruthy('Expected the snack bar to notify of action');

tick(500);
}));

it('should dismiss automatically after a specified timeout', fakeAsync(() => {
let dismissObservableCompleted = false;
let config = new MdSnackBarConfig();
config.duration = 250;
let snackBarRef = snackBar.open('content', 'test', config);
snackBarRef.afterDismissed().subscribe(null, null, () => {
dismissObservableCompleted = true;
});

viewContainerFixture.detectChanges();
flushMicrotasks();
expect(dismissObservableCompleted).toBeFalsy('Expected the snack bar not to be dismissed');

tick(1000);
viewContainerFixture.detectChanges();
flushMicrotasks();
expect(dismissObservableCompleted).toBeTruthy('Expected the snack bar to be dismissed');
}));
});

@Directive({selector: 'dir-with-view-container'})
Expand Down
10 changes: 8 additions & 2 deletions src/lib/snack-bar/snack-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import {MdSnackBarContainer} from './snack-bar-container';
import {SimpleSnackBar} from './simple-snack-bar';
import {extendObject} from '../core/util/object-extend';

// TODO(josephperrott): Automate dismiss after timeout.


/**
* Service to dispatch Material Design snack bar messages.
Expand Down Expand Up @@ -64,6 +62,14 @@ export class MdSnackBar {
} else {
snackBarRef.containerInstance.enter();
}

// If a dismiss timeout is provided, set up dismiss based on after the snackbar is opened.
if (config.duration > 0) {
snackBarRef.afterOpened().subscribe(() => {
setTimeout(() => snackBarRef.dismiss(), config.duration);
});
}

this._live.announce(config.announcementMessage, config.politeness);
this._snackBarRef = snackBarRef;
return this._snackBarRef;
Expand Down