Skip to content
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

feat(snackbar): don't require a ViewContainerRef #1783

Merged
merged 2 commits into from
Nov 9, 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
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