From 9115538691edf78ba9fc0111a1359016e4eec310 Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Wed, 9 Nov 2016 11:20:44 -0800 Subject: [PATCH] feat(snackbar): don't require a ViewContainerRef (#1783) * feat(snackbar): don't require a ViewContainerRef * default config, module method --- src/demo-app/snack-bar/snack-bar-demo.ts | 5 ++-- src/lib/snack-bar/snack-bar-config.ts | 14 ++++----- src/lib/snack-bar/snack-bar.spec.ts | 38 +++++++++++++++++------- src/lib/snack-bar/snack-bar.ts | 34 ++++++++++++++------- 4 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/demo-app/snack-bar/snack-bar-demo.ts b/src/demo-app/snack-bar/snack-bar-demo.ts index 72a8710984c6..e6ec6e2d6537 100644 --- a/src/demo-app/snack-bar/snack-bar-demo.ts +++ b/src/demo-app/snack-bar/snack-bar-demo.ts @@ -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, @@ -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); } } diff --git a/src/lib/snack-bar/snack-bar-config.ts b/src/lib/snack-bar/snack-bar-config.ts index 8eaebb8202cc..9317fb9e667d 100644 --- a/src/lib/snack-bar/snack-bar-config.ts +++ b/src/lib/snack-bar/snack-bar-config.ts @@ -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; } diff --git a/src/lib/snack-bar/snack-bar.spec.ts b/src/lib/snack-bar/snack-bar.spec.ts index f66fb300224a..90fced2e3a1f 100644 --- a/src/lib/snack-bar/snack-bar.spec.ts +++ b/src/lib/snack-bar/snack-bar.spec.ts @@ -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'; @@ -50,7 +49,7 @@ 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'); @@ -58,8 +57,25 @@ describe('MdSnackBar', () => { .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(); @@ -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(); @@ -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); @@ -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) @@ -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(); @@ -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(); @@ -159,7 +175,7 @@ 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; @@ -167,7 +183,7 @@ describe('MdSnackBar', () => { 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(); @@ -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(); diff --git a/src/lib/snack-bar/snack-bar.ts b/src/lib/snack-bar/snack-bar.ts index 20c9f0ec257a..03e26fd6b03b 100644 --- a/src/lib/snack-bar/snack-bar.ts +++ b/src/lib/snack-bar/snack-bar.ts @@ -39,14 +39,14 @@ export class MdSnackBar { * Creates and dispatches a snack bar with a custom component for the content, removing any * currently opened snack bars. */ - openFromComponent(component: ComponentType, - config: MdSnackBarConfig): MdSnackBarRef { + openFromComponent(component: ComponentType, config?: MdSnackBarConfig): MdSnackBarRef { + 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; }); @@ -54,28 +54,31 @@ export class MdSnackBar { // 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} */ - open(message: string, actionLabel: string, - config: MdSnackBarConfig): MdSnackBarRef { + open(message: string, action = '', config: MdSnackBarConfig = {}): MdSnackBarRef { 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; } @@ -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],