From bf0f625ee3260ba67b1b270e4837aac0b5bdf161 Mon Sep 17 00:00:00 2001 From: mmalerba Date: Thu, 9 Feb 2017 10:32:07 -0800 Subject: [PATCH] feat(dialog): support open with TemplateRef (#2910) * feat(dialog): support open with TemplateRef * add e2e test --- e2e/components/dialog/dialog.e2e.ts | 6 ++++++ src/demo-app/dialog/dialog-demo.html | 15 ++++++++++++-- src/demo-app/dialog/dialog-demo.ts | 10 ++++++++- src/e2e-app/dialog/dialog-e2e.html | 3 +++ src/e2e-app/dialog/dialog-e2e.ts | 8 ++++++- src/lib/dialog/dialog-container.ts | 31 +++++++++++++++++++++------- src/lib/dialog/dialog.ts | 31 ++++++++++++++++------------ 7 files changed, 79 insertions(+), 25 deletions(-) diff --git a/e2e/components/dialog/dialog.e2e.ts b/e2e/components/dialog/dialog.e2e.ts index 907713ceeaa..7c160f62b56 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/demo-app/dialog/dialog-demo.html b/src/demo-app/dialog/dialog-demo.html index 0391728e7cc..ec05d8f1ae0 100644 --- a/src/demo-app/dialog/dialog-demo.html +++ b/src/demo-app/dialog/dialog-demo.html @@ -1,7 +1,14 @@

Dialog demo

- - + + + @@ -59,3 +66,7 @@

Last close result: {{lastCloseResult}}

+ + diff --git a/src/demo-app/dialog/dialog-demo.ts b/src/demo-app/dialog/dialog-demo.ts index 3c447548750..f82ecbfbce4 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, MD_DIALOG_DATA} from '@angular/material'; @@ -27,6 +27,9 @@ export class DialogDemo { message: 'Jazzy jazz jazz' } }; + numTemplateOpens = 0; + + @ViewChild(TemplateRef) template: TemplateRef; constructor(public dialog: MdDialog, @Inject(DOCUMENT) doc: any) { // Possible useful example for the open and closeAll events. @@ -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); + } } diff --git a/src/e2e-app/dialog/dialog-e2e.html b/src/e2e-app/dialog/dialog-e2e.html index 827bfee99ca..b66e8dfd8c5 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 068406bbc5c..09f833d96d0 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({ diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index 9e99e661128..06576496cbd 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 4d4ed5c14b8..a4bd05585fe 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(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; }