Skip to content

Commit

Permalink
feat(dialog): add a config option for passing in data
Browse files Browse the repository at this point in the history
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 angular#2181.
  • Loading branch information
crisbeto committed Dec 18, 2016
1 parent 2168d7c commit 9591ab0
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 21 deletions.
8 changes: 7 additions & 1 deletion src/demo-app/dialog/dialog-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ <h2>Dialog position</h2>

<h2>Other options</h2>

<md-checkbox [(ngModel)]="config.disableClose">Disable close</md-checkbox>
<p>
<md-checkbox [(ngModel)]="config.disableClose">Disable close</md-checkbox>
</p>

<p>
<md-input [(ngModel)]="config.data.message" placeholder="Dialog message"></md-input>
</p>
</md-card-content>
</md-card>

Expand Down
15 changes: 9 additions & 6 deletions src/demo-app/dialog/dialog-demo.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -19,6 +19,9 @@ export class DialogDemo {
bottom: '',
left: '',
right: ''
},
data: {
message: 'Jazzy jazz jazz'
}
};

Expand All @@ -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;
});
Expand All @@ -44,13 +47,13 @@ export class DialogDemo {
template: `
<p>It's Jazz!</p>
<p><label>How much? <input #howMuch></label></p>
<p> {{ jazzMessage }} </p>
<p> {{ data.message }} </p>
<button type="button" (click)="dialogRef.close(howMuch.value)">Close dialog</button>`
})
export class JazzDialog {
jazzMessage = 'Jazzy jazz jazz';

constructor(public dialogRef: MdDialogRef<JazzDialog>) { }
constructor(
public dialogRef: MdDialogRef<JazzDialog>,
public data: MdDialogData) { }
}


Expand Down
24 changes: 15 additions & 9 deletions src/lib/dialog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,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

Expand Down Expand Up @@ -58,7 +59,10 @@ export class PizzaComponent {

openDialog() {
this.dialogRef = this.dialog.open(PizzaDialog, {
disableClose: false
disableClose: false,
data: {
pizzaType: 'pepperoni'
}
});

this.dialogRef.afterClosed().subscribe(result => {
Expand All @@ -71,7 +75,7 @@ export class PizzaComponent {
@Component({
selector: 'pizza-dialog',
template: `
<h1 md-dialog-title>Would you like to order pizza?</h1>
<h1 md-dialog-title>Would you like to order a {{ data.pizzaType }} pizza?</h1>
<md-dialog-actions>
<button (click)="dialogRef.close('yes')">Yes</button>
Expand All @@ -80,7 +84,9 @@ export class PizzaComponent {
`
})
export class PizzaDialog {
constructor(public dialogRef: MdDialogRef<PizzaDialog>) { }
constructor(
public dialogRef: MdDialogRef<PizzaDialog>,
public data: MdDialogData) { }
}
```

Expand Down
7 changes: 7 additions & 0 deletions src/lib/dialog/dialog-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
}
10 changes: 9 additions & 1 deletion src/lib/dialog/dialog-injector.ts
Original file line number Diff line number Diff line change
@@ -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<any>, private _parentInjector: Injector) { }
constructor(
private _dialogRef: MdDialogRef<any>,
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);
}
}
40 changes: 38 additions & 2 deletions src/lib/dialog/dialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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, {
Expand Down Expand Up @@ -384,19 +407,32 @@ class ContentElementDialog {
closeButtonAriaLabel: string;
}


/** 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 { }
5 changes: 3 additions & 2 deletions src/lib/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,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 = <MdDialogRef<T>> 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());
}
Expand All @@ -109,7 +110,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);

Expand Down

0 comments on commit 9591ab0

Please sign in to comment.