Skip to content

Commit db01363

Browse files
committed
feat(snack-bar): inject data and MdSnackBarRef into custom snack-bar component
* Injects the `MdSnackBarRef` into custom snack bar instances. * Adds the option to inject arbitrary data into a snack bar. * Turns the `DialogInjector` into a `PortalInjector` to allow it to be used elsewhere (e.g. the snack-bar). * Minor tweaks to the `SimpleSnackBar` to get it to use the new DI tokens instead of assigning data directly to the component instance. Fixes #5371.
1 parent 8817b4d commit db01363

File tree

9 files changed

+162
-78
lines changed

9 files changed

+162
-78
lines changed

src/lib/dialog/dialog-injector.ts renamed to src/lib/core/portal/portal-injector.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88

99
import {Injector} from '@angular/core';
1010

11-
/** Custom injector type specifically for instantiating components with a dialog. */
12-
export class DialogInjector implements Injector {
11+
/**
12+
* Custom injector to be used when providing custom
13+
* injection tokens to components inside a portal.
14+
* @docs-private
15+
*/
16+
export class PortalInjector implements Injector {
1317
constructor(
1418
private _parentInjector: Injector,
1519
private _customTokens: WeakMap<any, any>) { }

src/lib/dialog/dialog.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import {
2525
OverlayState,
2626
ComponentPortal,
2727
} from '../core';
28+
import {PortalInjector} from '../core/portal/portal-injector';
2829
import {extendObject} from '../core/util/object-extend';
2930
import {ESCAPE} from '../core/keyboard/keycodes';
30-
import {DialogInjector} from './dialog-injector';
3131
import {MdDialogConfig} from './dialog-config';
3232
import {MdDialogRef} from './dialog-ref';
3333
import {MdDialogContainer} from './dialog-container';
@@ -222,7 +222,7 @@ export class MdDialog {
222222
private _createInjector<T>(
223223
config: MdDialogConfig,
224224
dialogRef: MdDialogRef<T>,
225-
dialogContainer: MdDialogContainer): DialogInjector {
225+
dialogContainer: MdDialogContainer): PortalInjector {
226226

227227
let userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
228228
let injectionTokens = new WeakMap();
@@ -231,7 +231,7 @@ export class MdDialog {
231231
injectionTokens.set(MdDialogContainer, dialogContainer);
232232
injectionTokens.set(MD_DIALOG_DATA, config.data);
233233

234-
return new DialogInjector(userInjector || this._injector, injectionTokens);
234+
return new PortalInjector(userInjector || this._injector, injectionTokens);
235235
}
236236

237237
/**
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
{{message}}
2-
<button class="mat-simple-snackbar-action" *ngIf="hasAction" (click)="dismiss()">{{action}}</button>
1+
{{data.message}}
2+
3+
<button
4+
class="mat-simple-snackbar-action"
5+
*ngIf="hasAction"
6+
(click)="dismiss()">{{data.action}}</button>

src/lib/snack-bar/simple-snack-bar.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core';
10-
import {MdSnackBarRef} from './snack-bar-ref';
9+
import {Component, ViewEncapsulation, Inject, ChangeDetectionStrategy} from '@angular/core';
10+
import {MdSnackBarRef, MD_SNACK_BAR_DATA} from './index';
1111

1212

1313
/**
@@ -26,14 +26,9 @@ import {MdSnackBarRef} from './snack-bar-ref';
2626
}
2727
})
2828
export class SimpleSnackBar {
29-
/** The message to be shown in the snack bar. */
30-
message: string;
31-
32-
/** The label for the button in the snack bar. */
33-
action: string;
34-
35-
/** The instance of the component making up the content of the snack bar. */
36-
snackBarRef: MdSnackBarRef<SimpleSnackBar>;
29+
constructor(
30+
public snackBarRef: MdSnackBarRef<SimpleSnackBar>,
31+
@Inject(MD_SNACK_BAR_DATA) public data: { message: string, action: string }) { }
3732

3833
/** Dismisses the snack bar. */
3934
dismiss(): void {
@@ -42,6 +37,6 @@ export class SimpleSnackBar {
4237

4338
/** If the action button should be shown. */
4439
get hasAction(): boolean {
45-
return !!this.action;
40+
return !!this.data.action;
4641
}
4742
}

src/lib/snack-bar/snack-bar-config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,7 @@ export class MdSnackBarConfig {
3030

3131
/** Text layout direction for the snack bar. */
3232
direction?: Direction = 'ltr';
33+
34+
/** Data being injected into the child component. */
35+
data?: any = null;
3336
}

src/lib/snack-bar/snack-bar-ref.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,8 @@ import {MdSnackBarContainer} from './snack-bar-container';
1717
* Reference to a snack bar dispatched from the snack bar service.
1818
*/
1919
export class MdSnackBarRef<T> {
20-
private _instance: T;
21-
2220
/** The instance of the component making up the content of the snack bar. */
23-
get instance(): T {
24-
return this._instance;
25-
}
21+
instance: T;
2622

2723
/**
2824
* The instance of the component making up the content of the snack bar.
@@ -45,11 +41,8 @@ export class MdSnackBarRef<T> {
4541
*/
4642
private _durationTimeoutId: number;
4743

48-
constructor(instance: T,
49-
containerInstance: MdSnackBarContainer,
44+
constructor(containerInstance: MdSnackBarContainer,
5045
private _overlayRef: OverlayRef) {
51-
// Sets the readonly instance of the snack bar content component.
52-
this._instance = instance;
5346
this.containerInstance = containerInstance;
5447
// Dismiss snackbar on action.
5548
this.onAction().subscribe(() => this.dismiss());

src/lib/snack-bar/snack-bar.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ let snackBarRef = snackBar.open('Message archived', 'Undo');
1515
let snackBarRef = snackbar.openFromComponent(MessageArchivedComponent);
1616
```
1717

18-
In either case, an `MdSnackBarRef` is returned. This can be used to dismiss the snack-bar or to
19-
receive notification of when the snack-bar is dismissed. For simple messages with an action, the
18+
In either case, a `MdSnackBarRef` is returned. This can be used to dismiss the snack-bar or to
19+
receive notification of when the snack-bar is dismissed. For simple messages with an action, the
2020
`MdSnackBarRef` exposes an observable for when the action is triggered.
21+
If you want to close a custom snack-bar that was opened via `openFromComponent`, from within the
22+
component itself, you can inject the `MdSnackBarRef`.
2123

2224
```ts
2325
snackBarRef.afterDismissed().subscribe(() => {
@@ -33,7 +35,7 @@ snackBarRef.dismiss();
3335
```
3436

3537
### Dismissal
36-
A snack-bar can be dismissed manually by calling the `dismiss` method on the `MdSnackBarRef`
38+
A snack-bar can be dismissed manually by calling the `dismiss` method on the `MdSnackBarRef`
3739
returned from the call to `open`.
3840

3941
Only one snack-bar can ever be opened at one time. If a new snackbar is opened while a previous
@@ -45,3 +47,28 @@ snackbar.open('Message archived', 'Undo', {
4547
duration: 3000
4648
});
4749
```
50+
51+
### Sharing data with a custom snack-bar.
52+
You can share data with the custom snack-bar, that you opened via the `openFromComponent` method,
53+
by passing it through the `data` property.
54+
55+
```ts
56+
snackbar.openFromComponent(MessageArchivedComponent, {
57+
data: 'some data'
58+
});
59+
```
60+
61+
To access the data in your component, you have to use the `MD_SNACK_BAR_DATA` injection token:
62+
63+
```ts
64+
import {Component, Inject} from '@angular/core';
65+
import {MD_SNACK_BAR_DATA} from '@angular/material';
66+
67+
@Component({
68+
selector: 'your-snack-bar',
69+
template: 'passed in {{ data }}',
70+
})
71+
export class MessageArchivedComponent {
72+
constructor(@Inject(MD_SNACK_BAR_DATA) public data: any) { }
73+
}
74+
```

src/lib/snack-bar/snack-bar.spec.ts

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@ import {
77
flushMicrotasks,
88
tick
99
} from '@angular/core/testing';
10-
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
10+
import {NgModule, Component, Directive, ViewChild, ViewContainerRef, Inject} from '@angular/core';
1111
import {CommonModule} from '@angular/common';
1212
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
13-
import {MdSnackBarModule, MdSnackBar, MdSnackBarConfig, SimpleSnackBar} from './index';
1413
import {OverlayContainer, LiveAnnouncer} from '../core';
14+
import {
15+
MdSnackBarModule,
16+
MdSnackBar,
17+
MdSnackBarConfig,
18+
MdSnackBarRef,
19+
SimpleSnackBar,
20+
MD_SNACK_BAR_DATA,
21+
} from './index';
1522

1623

1724
// TODO(josephperrott): Update tests to mock waiting for time to complete for animations.
@@ -174,17 +181,6 @@ describe('MdSnackBar', () => {
174181
});
175182
}));
176183

177-
it('should open a custom component', () => {
178-
let config = {viewContainerRef: testViewContainerRef};
179-
let snackBarRef = snackBar.openFromComponent(BurritosNotification, config);
180-
181-
expect(snackBarRef.instance instanceof BurritosNotification)
182-
.toBe(true, 'Expected the snack bar content component to be BurritosNotification');
183-
expect(overlayContainerElement.textContent!.trim())
184-
.toBe('Burritos are on the way.',
185-
`Expected the overlay text content to be 'Burritos are on the way'`);
186-
});
187-
188184
it('should set the animation state to visible on entry', () => {
189185
let config = {viewContainerRef: testViewContainerRef};
190186
let snackBarRef = snackBar.open(simpleMessage, undefined, config);
@@ -361,6 +357,37 @@ describe('MdSnackBar', () => {
361357
expect(pane.getAttribute('dir')).toBe('rtl', 'Expected the pane to be in RTL mode.');
362358
});
363359

360+
describe('with custom component', () => {
361+
it('should open a custom component', () => {
362+
const snackBarRef = snackBar.openFromComponent(BurritosNotification);
363+
364+
expect(snackBarRef.instance instanceof BurritosNotification)
365+
.toBe(true, 'Expected the snack bar content component to be BurritosNotification');
366+
expect(overlayContainerElement.textContent!.trim())
367+
.toBe('Burritos are on the way.', 'Expected component to have the proper text.');
368+
});
369+
370+
it('should inject the snack bar reference into the component', () => {
371+
const snackBarRef = snackBar.openFromComponent(BurritosNotification);
372+
373+
expect(snackBarRef.instance.snackBarRef)
374+
.toBe(snackBarRef, 'Expected component to have an injected snack bar reference.');
375+
});
376+
377+
it('should be able to inject arbitrary user data', () => {
378+
const snackBarRef = snackBar.openFromComponent(BurritosNotification, {
379+
data: {
380+
burritoType: 'Chimichanga'
381+
}
382+
});
383+
384+
expect(snackBarRef.instance.data).toBeTruthy('Expected component to have a data object.');
385+
expect(snackBarRef.instance.data.burritoType)
386+
.toBe('Chimichanga', 'Expected the injected data object to be the one the user provided.');
387+
});
388+
389+
});
390+
364391
});
365392

366393
describe('MdSnackBar with parent MdSnackBar', () => {
@@ -453,7 +480,11 @@ class ComponentWithChildViewContainer {
453480

454481
/** Simple component for testing ComponentPortal. */
455482
@Component({template: '<p>Burritos are on the way.</p>'})
456-
class BurritosNotification {}
483+
class BurritosNotification {
484+
constructor(
485+
public snackBarRef: MdSnackBarRef<BurritosNotification>,
486+
@Inject(MD_SNACK_BAR_DATA) public data: any) { }
487+
}
457488

458489

459490
@Component({

0 commit comments

Comments
 (0)