Skip to content

Commit

Permalink
feat(dialog): support open with TemplateRef (#2910)
Browse files Browse the repository at this point in the history
* feat(dialog): support open with TemplateRef

* add e2e test
  • Loading branch information
mmalerba authored and tinayuangao committed Feb 9, 2017
1 parent 3bcb7c3 commit bf0f625
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 25 deletions.
6 changes: 6 additions & 0 deletions e2e/components/dialog/dialog.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ describe('dialog', () => {
expectToExist('md-dialog-container');
});

it('should open a template dialog', () => {
expectToExist('.my-template-dialog', false);
element(by.id('template')).click();
expectToExist('.my-template-dialog');
});

it('should close by clicking on the backdrop', () => {
element(by.id('default')).click();

Expand Down
15 changes: 13 additions & 2 deletions src/demo-app/dialog/dialog-demo.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
<h1>Dialog demo</h1>

<button md-raised-button color="primary" (click)="openJazz()" [disabled]="dialogRef">Open dialog</button>
<button md-raised-button color="accent" (click)="openContentElement()">Open dialog with content elements</button>
<button md-raised-button color="primary" (click)="openJazz()" [disabled]="dialogRef">
Open dialog
</button>
<button md-raised-button color="accent" (click)="openContentElement()">
Open dialog with content elements
</button>
<button md-raised-button color="accent" (click)="openTemplate()">
Open dialog with template content
</button>

<md-card class="demo-dialog-card">
<md-card-content>
Expand Down Expand Up @@ -59,3 +66,7 @@ <h2>Other options</h2>
</md-card>

<p>Last close result: {{lastCloseResult}}</p>

<template>
I'm a template dialog. I've been opened {{numTemplateOpens}} times!
</template>
10 changes: 9 additions & 1 deletion src/demo-app/dialog/dialog-demo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, Inject} from '@angular/core';
import {Component, Inject, ViewChild, TemplateRef} from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';
import {MdDialog, MdDialogRef, MdDialogConfig, MD_DIALOG_DATA} from '@angular/material';

Expand Down Expand Up @@ -27,6 +27,9 @@ export class DialogDemo {
message: 'Jazzy jazz jazz'
}
};
numTemplateOpens = 0;

@ViewChild(TemplateRef) template: TemplateRef<any>;

constructor(public dialog: MdDialog, @Inject(DOCUMENT) doc: any) {
// Possible useful example for the open and closeAll events.
Expand Down Expand Up @@ -55,6 +58,11 @@ export class DialogDemo {
let dialogRef = this.dialog.open(ContentElementDialog, this.config);
dialogRef.componentInstance.actionsAlignment = this.actionsAlignment;
}

openTemplate() {
this.numTemplateOpens++;
this.dialog.open(this.template, this.config);
}
}


Expand Down
3 changes: 3 additions & 0 deletions src/e2e-app/dialog/dialog-e2e.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
<button id="default" (click)="openDefault()">DEFAULT</button>
<button id="disabled" (click)="openDisabled()">DISABLED</button>
<button id="template" (click)="openTemplate()">TEMPLATE</button>

<template><div class="my-template-dialog">my template dialog</div></template>
8 changes: 7 additions & 1 deletion src/e2e-app/dialog/dialog-e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component} from '@angular/core';
import {Component, ViewChild, TemplateRef} from '@angular/core';
import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material';

@Component({
Expand All @@ -9,6 +9,8 @@ import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material';
export class DialogE2E {
dialogRef: MdDialogRef<TestDialog>;

@ViewChild(TemplateRef) templateRef: TemplateRef<any>;

constructor (private _dialog: MdDialog) { }

private _openDialog(config?: MdDialogConfig) {
Expand All @@ -28,6 +30,10 @@ export class DialogE2E {
disableClose: true
});
}

openTemplate() {
this.dialogRef = this._dialog.open(this.templateRef);
}
}

@Component({
Expand Down
31 changes: 23 additions & 8 deletions src/lib/dialog/dialog-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
}

/**
* Attach a portal as content to this dialog container.
* Attach a ComponentPortal as content to this dialog container.
* @param portal Portal to be attached as the dialog content.
*/
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
Expand All @@ -61,21 +61,36 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy {
}

let attachResult = this._portalHost.attachComponentPortal(portal);
this._trapFocus();
return attachResult;
}

/**
* Attach a TemplatePortal as content to this dialog container.
* @param portal Portal to be attached as the dialog content.
*/
attachTemplatePortal(portal: TemplatePortal): Map<string, any> {
if (this._portalHost.hasAttached()) {
throw new MdDialogContentAlreadyAttachedError();
}

let attachedResult = this._portalHost.attachTemplatePortal(portal);
this._trapFocus();
return attachedResult;
}

/**
* Moves the focus inside the focus trap.
* @private
*/
private _trapFocus() {
// If were to attempt to focus immediately, then the content of the dialog would not yet be
// ready in instances where change detection has to run first. To deal with this, we simply
// wait for the microtask queue to be empty.
this._ngZone.onMicrotaskEmpty.first().subscribe(() => {
this._elementFocusedBeforeDialogWasOpened = document.activeElement;
this._focusTrap.focusFirstTabbableElement();
});

return attachResult;
}

/** @docs-private */
attachTemplatePortal(portal: TemplatePortal): Map<string, any> {
throw Error('Not yet implemented');
}

/**
Expand Down
31 changes: 18 additions & 13 deletions src/lib/dialog/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import {Injector, ComponentRef, Injectable, Optional, SkipSelf} from '@angular/core';
import {Injector, ComponentRef, Injectable, Optional, SkipSelf, TemplateRef} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';

import {Overlay, OverlayRef, ComponentType, OverlayState, ComponentPortal} from '../core';
import {extendObject} from '../core/util/object-extend';

import {DialogInjector} from './dialog-injector';
import {MdDialogConfig} from './dialog-config';
import {MdDialogRef} from './dialog-ref';
import {MdDialogContainer} from './dialog-container';
import {TemplatePortal} from '../core/portal/portal';


// TODO(jelbourn): add support for opening with a TemplateRef
// TODO(jelbourn): animations


Expand Down Expand Up @@ -53,16 +51,19 @@ export class MdDialog {

/**
* Opens a modal dialog containing the given component.
* @param component Type of the component to load into the load.
* @param componentOrTemplateRef Type of the component to load into the dialog,
* or a TemplateRef to instantiate as the dialog content.
* @param config Extra configuration options.
* @returns Reference to the newly-opened dialog.
*/
open<T>(component: ComponentType<T>, config?: MdDialogConfig): MdDialogRef<T> {
open<T>(componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
config?: MdDialogConfig): MdDialogRef<T> {
config = _applyConfigDefaults(config);

let overlayRef = this._createOverlay(config);
let dialogContainer = this._attachDialogContainer(overlayRef, config);
let dialogRef = this._attachDialogContent(component, dialogContainer, overlayRef, config);
let dialogRef =
this._attachDialogContent(componentOrTemplateRef, dialogContainer, overlayRef, config);

this._openDialogs.push(dialogRef);
dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef));
Expand Down Expand Up @@ -114,14 +115,15 @@ export class MdDialog {

/**
* Attaches the user-provided component to the already-created MdDialogContainer.
* @param component The type of component being loaded into the dialog.
* @param componentOrTemplateRef The type of component being loaded into the dialog,
* or a TemplateRef to instantiate as the content.
* @param dialogContainer Reference to the wrapping MdDialogContainer.
* @param overlayRef Reference to the overlay in which the dialog resides.
* @param config The dialog configuration.
* @returns A promise resolving to the MdDialogRef that should be returned to the user.
*/
private _attachDialogContent<T>(
component: ComponentType<T>,
componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
dialogContainer: MdDialogContainer,
overlayRef: OverlayRef,
config?: MdDialogConfig): MdDialogRef<T> {
Expand All @@ -143,10 +145,13 @@ export class MdDialog {
let userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
let dialogInjector = new DialogInjector(userInjector || this._injector, dialogRef, config.data);

let contentPortal = new ComponentPortal(component, null, dialogInjector);

let contentRef = dialogContainer.attachComponentPortal(contentPortal);
dialogRef.componentInstance = contentRef.instance;
if (componentOrTemplateRef instanceof TemplateRef) {
dialogContainer.attachTemplatePortal(new TemplatePortal(componentOrTemplateRef, null));
} else {
let contentRef = dialogContainer.attachComponentPortal(
new ComponentPortal(componentOrTemplateRef, null, dialogInjector));
dialogRef.componentInstance = contentRef.instance;
}

return dialogRef;
}
Expand Down

0 comments on commit bf0f625

Please sign in to comment.