From 7b6c7190c2d2367f78a1584b45377dea364822ce Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 2 Feb 2017 16:05:48 -0800 Subject: [PATCH 1/2] feat(dialog): support open with TemplateRef --- src/demo-app/dialog/dialog-demo.html | 15 ++++++++++++-- src/demo-app/dialog/dialog-demo.ts | 10 ++++++++- src/lib/dialog/dialog-container.ts | 31 +++++++++++++++++++++------- src/lib/dialog/dialog.ts | 31 ++++++++++++++++------------ 4 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/demo-app/dialog/dialog-demo.html b/src/demo-app/dialog/dialog-demo.html index 53734bbbfdff..8b87d6e04d07 100644 --- a/src/demo-app/dialog/dialog-demo.html +++ b/src/demo-app/dialog/dialog-demo.html @@ -1,7 +1,14 @@

Dialog demo

- - + + + @@ -51,3 +58,7 @@

Other options

Last close result: {{lastCloseResult}}

+ + diff --git a/src/demo-app/dialog/dialog-demo.ts b/src/demo-app/dialog/dialog-demo.ts index 5e91c097f9aa..68a4c81fac82 100644 --- a/src/demo-app/dialog/dialog-demo.ts +++ b/src/demo-app/dialog/dialog-demo.ts @@ -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} from '@angular/material'; @@ -23,6 +23,9 @@ export class DialogDemo { right: '' } }; + numTemplateOpens = 0; + + @ViewChild(TemplateRef) template: TemplateRef; constructor(public dialog: MdDialog, @Inject(DOCUMENT) doc: any) { // Possible useful example for the open and closeAll events. @@ -51,6 +54,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); + } } diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index 415c6d31c1bb..c9aa19dcec04 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -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(portal: ComponentPortal): ComponentRef { @@ -61,7 +61,29 @@ 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 { + 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. @@ -69,13 +91,6 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy { this._elementFocusedBeforeDialogWasOpened = document.activeElement; this._focusTrap.focusFirstTabbableElement(); }); - - return attachResult; - } - - /** @docs-private */ - attachTemplatePortal(portal: TemplatePortal): Map { - throw Error('Not yet implemented'); } /** diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts index 93e48cd6b2fc..95ea356b8d4c 100644 --- a/src/lib/dialog/dialog.ts +++ b/src/lib/dialog/dialog.ts @@ -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 @@ -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(component: ComponentType, config?: MdDialogConfig): MdDialogRef { + open(componentOrTemplateRef: ComponentType | TemplateRef, + config?: MdDialogConfig): MdDialogRef { 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)); @@ -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( - component: ComponentType, + componentOrTemplateRef: ComponentType | TemplateRef, dialogContainer: MdDialogContainer, overlayRef: OverlayRef, config?: MdDialogConfig): MdDialogRef { @@ -143,10 +145,13 @@ export class MdDialog { let userInjector = config && config.viewContainerRef && config.viewContainerRef.injector; let dialogInjector = new DialogInjector(dialogRef, userInjector || this._injector); - 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; } From 8b64328113f6b10248b64b1d9500c30a52b51765 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 3 Feb 2017 13:42:59 -0800 Subject: [PATCH 2/2] add e2e test --- e2e/components/dialog/dialog.e2e.ts | 6 ++++++ src/e2e-app/dialog/dialog-e2e.html | 3 +++ src/e2e-app/dialog/dialog-e2e.ts | 8 +++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/e2e/components/dialog/dialog.e2e.ts b/e2e/components/dialog/dialog.e2e.ts index 907713ceeaae..7c160f62b561 100644 --- a/e2e/components/dialog/dialog.e2e.ts +++ b/e2e/components/dialog/dialog.e2e.ts @@ -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(); diff --git a/src/e2e-app/dialog/dialog-e2e.html b/src/e2e-app/dialog/dialog-e2e.html index 827bfee99ca0..b66e8dfd8c51 100644 --- a/src/e2e-app/dialog/dialog-e2e.html +++ b/src/e2e-app/dialog/dialog-e2e.html @@ -1,2 +1,5 @@ + + + diff --git a/src/e2e-app/dialog/dialog-e2e.ts b/src/e2e-app/dialog/dialog-e2e.ts index 068406bbc5cb..09f833d96d0b 100644 --- a/src/e2e-app/dialog/dialog-e2e.ts +++ b/src/e2e-app/dialog/dialog-e2e.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, ViewChild, TemplateRef} from '@angular/core'; import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material'; @Component({ @@ -9,6 +9,8 @@ import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material'; export class DialogE2E { dialogRef: MdDialogRef; + @ViewChild(TemplateRef) templateRef: TemplateRef; + constructor (private _dialog: MdDialog) { } private _openDialog(config?: MdDialogConfig) { @@ -28,6 +30,10 @@ export class DialogE2E { disableClose: true }); } + + openTemplate() { + this.dialogRef = this._dialog.open(this.templateRef); + } } @Component({