Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dialog): support open with TemplateRef #2910

Merged
merged 2 commits into from
Feb 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -51,3 +58,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} from '@angular/material';

Expand All @@ -23,6 +23,9 @@ export class DialogDemo {
right: ''
}
};
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 @@ -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);
}
}


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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove the @private part of the jsdoc since its in the function signature

*/
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(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;
}
Expand Down