Skip to content

Commit

Permalink
feat(snackbar): don't require a ViewContainerRef (#1783)
Browse files Browse the repository at this point in the history
* feat(snackbar): don't require a ViewContainerRef

* default config, module method
  • Loading branch information
jelbourn authored Nov 9, 2016
1 parent f1f660e commit 9115538
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 33 deletions.
5 changes: 2 additions & 3 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, ViewContainerRef} from '@angular/core';
import {MdSnackBar, MdSnackBarConfig} from '@angular/material';
import {MdSnackBar} from '@angular/material';

@Component({
moduleId: module.id,
Expand All @@ -16,7 +16,6 @@ export class SnackBarDemo {
public viewContainerRef: ViewContainerRef) { }

open() {
let config = new MdSnackBarConfig(this.viewContainerRef);
this.snackBar.open(this.message, this.action && this.actionButtonLabel, config);
this.snackBar.open(this.message, this.action && this.actionButtonLabel);
}
}
14 changes: 6 additions & 8 deletions src/lib/snack-bar/snack-bar-config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import {ViewContainerRef} from '@angular/core';
import {AriaLivePoliteness} from '../core';


/**
* Configuration used when opening a snack-bar.
*/
export class MdSnackBarConfig {
/** The politeness level for the MdAriaLiveAnnouncer announcement. */
politeness: AriaLivePoliteness = 'assertive';
politeness?: AriaLivePoliteness = 'assertive';

/** Message to be announced by the MdAriaLiveAnnouncer */
announcementMessage: string;
announcementMessage?: string = '';

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

constructor(viewContainerRef: ViewContainerRef) {
this.viewContainerRef = viewContainerRef;
}
viewContainerRef?: ViewContainerRef = null;
}
38 changes: 27 additions & 11 deletions src/lib/snack-bar/snack-bar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {inject, async, ComponentFixture, TestBed} from '@angular/core/testing';
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
import {MdSnackBar, MdSnackBarModule} from './snack-bar';
import {OverlayContainer, MdLiveAnnouncer} from '../core';
import {MdSnackBarConfig} from './snack-bar-config';
import {SimpleSnackBar} from './simple-snack-bar';


Expand Down Expand Up @@ -50,16 +49,33 @@ describe('MdSnackBar', () => {
});

it('should have the role of alert', () => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
snackBar.open(simpleMessage, simpleActionLabel, config);

let containerElement = overlayContainerElement.querySelector('snack-bar-container');
expect(containerElement.getAttribute('role'))
.toBe('alert', 'Expected snack bar container to have role="alert"');
});

it('should open and close a snackbar without a ViewContainerRef', async(() => {
let snackBarRef = snackBar.open('Snack time!', 'CHEW');
viewContainerFixture.detectChanges();

let messageElement = overlayContainerElement.querySelector('.md-simple-snackbar-message');
expect(messageElement.textContent)
.toBe('Snack time!', 'Expected snack bar to show a message without a ViewContainerRef');

snackBarRef.dismiss();
viewContainerFixture.detectChanges();

viewContainerFixture.whenStable().then(() => {
expect(overlayContainerElement.childNodes.length)
.toBe(0, 'Expected snack bar to be dismissed without a ViewContainerRef');
});
}));

it('should open a simple message with a button', () => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, simpleActionLabel, config);

viewContainerFixture.detectChanges();
Expand All @@ -84,7 +100,7 @@ describe('MdSnackBar', () => {
});

it('should open a simple message with no button', () => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, null, config);

viewContainerFixture.detectChanges();
Expand All @@ -104,7 +120,7 @@ describe('MdSnackBar', () => {
});

it('should dismiss the snack bar and remove itself from the view', async(() => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
let dismissObservableCompleted = false;

let snackBarRef = snackBar.open(simpleMessage, null, config);
Expand All @@ -127,7 +143,7 @@ describe('MdSnackBar', () => {
}));

it('should open a custom component', () => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.openFromComponent(BurritosNotification, config);

expect(snackBarRef.instance)
Expand All @@ -139,7 +155,7 @@ describe('MdSnackBar', () => {
});

it('should set the animation state to visible on entry', () => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, null, config);

viewContainerFixture.detectChanges();
Expand All @@ -148,7 +164,7 @@ describe('MdSnackBar', () => {
});

it('should set the animation state to complete on exit', () => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, null, config);
snackBarRef.dismiss();

Expand All @@ -159,15 +175,15 @@ describe('MdSnackBar', () => {

it(`should set the old snack bar animation state to complete and the new snack bar animation
state to visible on entry of new snack bar`, async(() => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, null, config);
let dismissObservableCompleted = false;

viewContainerFixture.detectChanges();
expect(snackBarRef.containerInstance.animationState)
.toBe('visible', `Expected the animation state would be 'visible'.`);

let config2 = new MdSnackBarConfig(testViewContainerRef);
let config2 = {viewContainerRef: testViewContainerRef};
let snackBarRef2 = snackBar.open(simpleMessage, null, config2);

viewContainerFixture.detectChanges();
Expand All @@ -185,7 +201,7 @@ describe('MdSnackBar', () => {
}));

it('should open a new snackbar after dismissing a previous snackbar', async(() => {
let config = new MdSnackBarConfig(testViewContainerRef);
let config = {viewContainerRef: testViewContainerRef};
let snackBarRef = snackBar.open(simpleMessage, 'DISMISS', config);
viewContainerFixture.detectChanges();

Expand Down
34 changes: 23 additions & 11 deletions src/lib/snack-bar/snack-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,43 +39,46 @@ export class MdSnackBar {
* Creates and dispatches a snack bar with a custom component for the content, removing any
* currently opened snack bars.
*/
openFromComponent<T>(component: ComponentType<T>,
config: MdSnackBarConfig): MdSnackBarRef<T> {
openFromComponent<T>(component: ComponentType<T>, config?: MdSnackBarConfig): MdSnackBarRef<T> {
config = _applyConfigDefaults(config);
let overlayRef = this._createOverlay();
let snackBarContainer = this._attachSnackBarContainer(overlayRef, config);
let mdSnackBarRef = this._attachSnackbarContent(component, snackBarContainer, overlayRef);
let snackBarRef = this._attachSnackbarContent(component, snackBarContainer, overlayRef);

// When the snackbar is dismissed, clear the reference to it.
mdSnackBarRef.afterDismissed().subscribe(() => {
snackBarRef.afterDismissed().subscribe(() => {
this._snackBarRef = null;
});

// If a snack bar is already in view, dismiss it and enter the new snack bar after exit
// animation is complete.
if (this._snackBarRef) {
this._snackBarRef.afterDismissed().subscribe(() => {
mdSnackBarRef.containerInstance.enter();
snackBarRef.containerInstance.enter();
});
this._snackBarRef.dismiss();
// If no snack bar is in view, enter the new snack bar.
} else {
mdSnackBarRef.containerInstance.enter();
snackBarRef.containerInstance.enter();
}
this._live.announce(config.announcementMessage, config.politeness);
this._snackBarRef = mdSnackBarRef;
this._snackBarRef = snackBarRef;
return this._snackBarRef;
}

/**
* Creates and dispatches a snack bar.
* Opens a snackbar with a message and an optional action.
* @param message The message to show in the snackbar.
* @param action The label for the snackbar action.
* @param config Additional configuration options for the snackbar.
* @returns {MdSnackBarRef<SimpleSnackBar>}
*/
open(message: string, actionLabel: string,
config: MdSnackBarConfig): MdSnackBarRef<SimpleSnackBar> {
open(message: string, action = '', config: MdSnackBarConfig = {}): MdSnackBarRef<SimpleSnackBar> {
config.announcementMessage = message;
let simpleSnackBarRef = this.openFromComponent(SimpleSnackBar, config);
simpleSnackBarRef.instance.snackBarRef = simpleSnackBarRef;
simpleSnackBarRef.instance.message = message;
simpleSnackBarRef.instance.action = actionLabel;
simpleSnackBarRef.instance.action = action;
return simpleSnackBarRef;
}

Expand Down Expand Up @@ -115,6 +118,15 @@ export class MdSnackBar {
}
}

/**
* Applies default options to the snackbar config.
* @param config The configuration to which the defaults will be applied.
* @returns The new configuration object with defaults applied.
*/
function _applyConfigDefaults(config: MdSnackBarConfig): MdSnackBarConfig {
return Object.assign(new MdSnackBarConfig(), config);
}


@NgModule({
imports: [OverlayModule, PortalModule, CommonModule],
Expand Down

0 comments on commit 9115538

Please sign in to comment.