From 5f682e2edd906c0bc4b15633d5c4fd26eaeca3d7 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sun, 18 Dec 2016 15:25:49 +0200 Subject: [PATCH] feat(dialog): add a config option for passing in data Adds the ability to pass in data to a dialog via the `data` property. While this was previously possible through the `dialogRef.componentInstance.someUserSuppliedProperty`, it wasn't the most intuitive. The new approach looks like this: ``` dialog.open(SomeDialog, { data: { hello: 'world' } }); class SometDialog { constructor(data: MdDialogData) { console.log(data['hello']); } } ``` Fixes #2181. --- src/demo-app/dialog/dialog-demo.html | 8 +++++- src/demo-app/dialog/dialog-demo.ts | 15 ++++++----- src/lib/dialog/README.md | 24 ++++++++++------- src/lib/dialog/dialog-config.ts | 7 +++++ src/lib/dialog/dialog-injector.ts | 10 ++++++- src/lib/dialog/dialog.spec.ts | 39 ++++++++++++++++++++++++++-- src/lib/dialog/dialog.ts | 5 ++-- 7 files changed, 87 insertions(+), 21 deletions(-) diff --git a/src/demo-app/dialog/dialog-demo.html b/src/demo-app/dialog/dialog-demo.html index 491e8031393f..f6e44a425105 100644 --- a/src/demo-app/dialog/dialog-demo.html +++ b/src/demo-app/dialog/dialog-demo.html @@ -26,7 +26,13 @@

Dialog position

Other options

- Disable close +

+ Disable close +

+ +

+ +

diff --git a/src/demo-app/dialog/dialog-demo.ts b/src/demo-app/dialog/dialog-demo.ts index 7323d8f67173..16620a20109b 100644 --- a/src/demo-app/dialog/dialog-demo.ts +++ b/src/demo-app/dialog/dialog-demo.ts @@ -1,5 +1,5 @@ import {Component} from '@angular/core'; -import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material'; +import {MdDialog, MdDialogRef, MdDialogConfig, MdDialogData} from '@angular/material'; @Component({ moduleId: module.id, @@ -19,6 +19,9 @@ export class DialogDemo { bottom: '', left: '', right: '' + }, + data: { + message: 'Jazzy jazz jazz' } }; @@ -27,7 +30,7 @@ export class DialogDemo { openJazz() { this.dialogRef = this.dialog.open(JazzDialog, this.config); - this.dialogRef.afterClosed().subscribe(result => { + this.dialogRef.afterClosed().subscribe((result: string) => { this.lastCloseResult = result; this.dialogRef = null; }); @@ -44,13 +47,13 @@ export class DialogDemo { template: `

It's Jazz!

-

{{ jazzMessage }}

+

{{ data.message }}

` }) export class JazzDialog { - jazzMessage = 'Jazzy jazz jazz'; - - constructor(public dialogRef: MdDialogRef) { } + constructor( + public dialogRef: MdDialogRef, + public data: MdDialogData) { } } diff --git a/src/lib/dialog/README.md b/src/lib/dialog/README.md index 1b0a150dbe33..a36eeb6778c7 100644 --- a/src/lib/dialog/README.md +++ b/src/lib/dialog/README.md @@ -13,12 +13,13 @@ MdDialog is a service, which opens dialogs components in the view. | Key | Description | | --- | ------------ | -| `role: DialogRole = 'dialog'` | The ARIA role of the dialog element. Possible values are `dialog` and `alertdialog`. Optional. | -| `disableClose: boolean = false` | Whether to prevent the user from closing a dialog by clicking on the backdrop or pressing escape. Optional. | -| `width: string = ''` | Width of the dialog. Takes any valid CSS value. Optional. | -| `height: string = ''` | Height of the dialog. Takes any valid CSS value. Optional. | -| `position: { top?: string, bottom?: string, left?: string, right?: string }` | Position of the dialog that overrides the default centering in it's axis. Optional. | -| `viewContainerRef: ViewContainerRef` | The view container ref to attach the dialog to. Optional. | +| `role?: DialogRole = 'dialog'` | The ARIA role of the dialog element. Possible values are `dialog` and `alertdialog`. | +| `disableClose?: boolean = false` | Whether to prevent the user from closing a dialog by clicking on the backdrop or pressing escape. | +| `data?: { [key: string]: any }` | Data that will be injected into the dialog as `MdDialogData`. | +| `width?: string = ''` | Width of the dialog. Takes any valid CSS value. | +| `height?: string = ''` | Height of the dialog. Takes any valid CSS value. | +| `position?: { top?: string, bottom?: string, left?: string, right?: string }` | Position of the dialog that overrides the default centering in it's axis. | +| `viewContainerRef?: ViewContainerRef` | The view container ref to attach the dialog to. | ## MdDialogRef @@ -57,7 +58,10 @@ export class PizzaComponent { openDialog() { this.dialogRef = this.dialog.open(PizzaDialog, { - disableClose: false + disableClose: false, + data: { + pizzaType: 'pepperoni' + } }); this.dialogRef.afterClosed().subscribe(result => { @@ -70,7 +74,7 @@ export class PizzaComponent { @Component({ selector: 'pizza-dialog', template: ` -

Would you like to order pizza?

+

Would you like to order a {{ data.pizzaType }} pizza?

@@ -79,7 +83,9 @@ export class PizzaComponent { ` }) export class PizzaDialog { - constructor(public dialogRef: MdDialogRef) { } + constructor( + public dialogRef: MdDialogRef, + public data: MdDialogData) { } } ``` diff --git a/src/lib/dialog/dialog-config.ts b/src/lib/dialog/dialog-config.ts index 070ed8272876..52e0a9786853 100644 --- a/src/lib/dialog/dialog-config.ts +++ b/src/lib/dialog/dialog-config.ts @@ -11,6 +11,10 @@ export interface DialogPosition { right?: string; }; +/** Data to be injected into a dialog. */ +export class MdDialogData { + [key: string]: any; +} /** * Configuration for opening a modal dialog with the MdDialog service. @@ -33,5 +37,8 @@ export class MdDialogConfig { /** Position overrides. */ position?: DialogPosition; + /** Data being injected into the child component. */ + data?: MdDialogData; + // TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling. } diff --git a/src/lib/dialog/dialog-injector.ts b/src/lib/dialog/dialog-injector.ts index 76f589e66066..839170524463 100644 --- a/src/lib/dialog/dialog-injector.ts +++ b/src/lib/dialog/dialog-injector.ts @@ -1,16 +1,24 @@ import {Injector} from '@angular/core'; import {MdDialogRef} from './dialog-ref'; +import {MdDialogData} from './dialog-config'; /** Custom injector type specifically for instantiating components with a dialog. */ export class DialogInjector implements Injector { - constructor(private _dialogRef: MdDialogRef, private _parentInjector: Injector) { } + constructor( + private _dialogRef: MdDialogRef, + private _data: MdDialogData, + private _parentInjector: Injector) { } get(token: any, notFoundValue?: any): any { if (token === MdDialogRef) { return this._dialogRef; } + if (token === MdDialogData && this._data) { + return this._data; + } + return this._parentInjector.get(token, notFoundValue); } } diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts index 38243742b63c..c4d2b2008625 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -14,6 +14,7 @@ import {MdDialog} from './dialog'; import {OverlayContainer} from '../core'; import {MdDialogRef} from './dialog-ref'; import {MdDialogContainer} from './dialog-container'; +import {MdDialogData} from './dialog-config'; describe('MdDialog', () => { @@ -227,6 +228,28 @@ describe('MdDialog', () => { expect(overlayContainerElement.querySelectorAll('md-dialog-container').length).toBe(0); }); + describe('passing in data', () => { + it('should be able to pass in data', () => { + let config = { + data: { + stringParam: 'hello', + dateParam: new Date() + } + }; + + let instance = dialog.open(DialogWithInjectedData, config).componentInstance; + + expect(instance.data['stringParam']).toBe(config.data.stringParam); + expect(instance.data['dateParam']).toBe(config.data.dateParam); + }); + + it('should throw if injected data is expected but none is passed', () => { + expect(() => { + dialog.open(DialogWithInjectedData); + }).toThrow(); + }); + }); + describe('disableClose option', () => { it('should prevent closing via clicks on the backdrop', () => { dialog.open(PizzaMsg, { @@ -454,19 +477,31 @@ class ComponentThatProvidesMdDialog { constructor(public dialog: MdDialog) {} } +/** Simple component for testing ComponentPortal. */ +@Component({template: ''}) +class DialogWithInjectedData { + constructor(public data: MdDialogData) { } +} + // Create a real (non-test) NgModule as a workaround for // https://github.com/angular/angular/issues/10760 const TEST_DIRECTIVES = [ ComponentWithChildViewContainer, PizzaMsg, DirectiveWithViewContainer, - ContentElementDialog + ContentElementDialog, + DialogWithInjectedData ]; @NgModule({ imports: [MdDialogModule], exports: TEST_DIRECTIVES, declarations: TEST_DIRECTIVES, - entryComponents: [ComponentWithChildViewContainer, PizzaMsg, ContentElementDialog], + entryComponents: [ + ComponentWithChildViewContainer, + PizzaMsg, + ContentElementDialog, + DialogWithInjectedData + ], }) class DialogTestModule { } diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts index 38c51ef6be59..53d77b822695 100644 --- a/src/lib/dialog/dialog.ts +++ b/src/lib/dialog/dialog.ts @@ -105,8 +105,9 @@ export class MdDialog { // Create a reference to the dialog we're creating in order to give the user a handle // to modify and close it. let dialogRef = > new MdDialogRef(overlayRef); + let config: MdDialogConfig = dialogContainer.dialogConfig; - if (!dialogContainer.dialogConfig.disableClose) { + if (!config.disableClose) { // When the dialog backdrop is clicked, we want to close it. overlayRef.backdropClick().first().subscribe(() => dialogRef.close()); } @@ -117,7 +118,7 @@ export class MdDialog { // We create an injector specifically for the component we're instantiating so that it can // inject the MdDialogRef. This allows a component loaded inside of a dialog to close itself // and, optionally, to return a value. - let dialogInjector = new DialogInjector(dialogRef, this._injector); + let dialogInjector = new DialogInjector(dialogRef, config.data, this._injector); let contentPortal = new ComponentPortal(component, null, dialogInjector);