Components: Dialog with inline Template

Created on 17 Mar 2018  路  12Comments  路  Source: angular/components

Bug, feature request, or proposal:

feature request

What is the expected behavior?

I'd like to use something like this (just like mat-menu):

<button (click)="myDialog.open()">Open it</button>

<mat-dialog (result)="onResult($event)" #myDialog>
  <h1 mat-dialog-title>Dialog Title</h1>
    <mat-dialog-content>
        Content
    </mat-dialog-content>
    <mat-dialog-actions>
        <button mat-button [mat-dialog-close]="false">Cancel</button>
        <button mat-button [mat-dialog-close]="true">Confirm</button>
    </mat-dialog-actions>
</mat-dialog>

What is the current behavior?

Doesn't exist as fat as I have seen in the docs and source code.

What are the steps to reproduce?

I've created a small component which demonstrates what I'm looking for:
https://stackblitz.com/edit/angular-material2-issue-tp5aej?file=app%2Fapp.component.html
(I didn't get mat-dialog-close working with the projection, though I chose to just add a method to my dialog component)

What is the use-case or motivation for changing an existing behavior?

There are many use cases where I just want a very simple dialog, and don't want to write 30 lines of code.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular 6 beta
Material 6 beta

P4 materiadialog feature

Most helpful comment

I would like to add that if you need MatDialogRef instance in your dialog component in the <ng-template> pattern @Chan4077 suggested above, it's as easy as:

<ng-template #myDialog let-dialogRef="dialogRef">
  <my-dialog [dialogRef]="dialogRef"></my-dialog>
</ng-template>

I like this usage and I think it would be nice if the usage is clearly documented.

All 12 comments

This can be done using <ng-template>:

app.component.html:

<button mat-button (click)="openDialogWithRef(myDialog)">Open dialog with template ref</button>
<!-- or... -->
<button mat-button (click)="openDialogWithoutRef()">Open dialog without template ref</button>
<ng-template #myDialog>
  <h2 matDialogTitle>Dialog!</h2>
  <mat-dialog-content>
    <p>Dialog content goes here</p>
  </mat-dialog-content>
  <mat-dialog-actions align="end">
    <button mat-button matDialogClose color="primary">DISMISS</button>
  </mat-dialog-actions>
</ng-template>
<ng-template #secondDialog>
  <h2 matDialogTitle>Such dialog very wow!</h2>
  <mat-dialog-content>
    <p><em>*insert meme here*</em></p>
  </mat-dialog-content>
  <mat-dialog-actions align="end">
    <button mat-button matDialogClose color="primary">Shutdown dialog</button>
  </mat-dialog-actions>
</ng-template>

app.component.ts:

import { ViewChild, TemplateRef } from '@angular/core';
import { MatDialog } from '@angular/material';

// ...
export class AppComponent {
  @ViewChild('secondDialog') secondDialog: TemplateRef<any>;
  constructor(private dialog: MatDialog) { }
  openDialogWithRef(templateRef: TemplateRef<any>) {
    this.dialog.open(templateRef);
  }
  openDialogWithoutRef() {
    this.dialog.open(this.secondDialog);
  }
}

Demo
See the code in the snippet below for more info:
https://github.com/angular/components/blob/c28549d088c4462bf64078db5442a123542bf1be/src/lib/dialog/dialog.ts#L105-L113

Thank you.

But your solution doesn't support the Dialog results. Using dialogRef.afterClosed().subscribe(...) will make the code even larger, and I guess it's not that easy to say #myDialog1 should call onFirstResult($event) and #myDialog2 should call onSecondResult($event).

I would like to add that if you need MatDialogRef instance in your dialog component in the <ng-template> pattern @Chan4077 suggested above, it's as easy as:

<ng-template #myDialog let-dialogRef="dialogRef">
  <my-dialog [dialogRef]="dialogRef"></my-dialog>
</ng-template>

I like this usage and I think it would be nice if the usage is clearly documented.

This has been solved in #9379 even before this issue came up.
A naive wrapper component could look like this

import {  ChangeDetectionStrategy,  Component,  ContentChild,  EventEmitter,  Output,  TemplateRef } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material';

@Component({
  selector: 'app-dialog',
  templateUrl: './dialog.component.html',
  styleUrls: ['./dialog.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DialogComponent {
  @ContentChild(TemplateRef) template: TemplateRef<{}>;
  @Output() result = new EventEmitter();

  dRef: MatDialogRef<any>;

  constructor (private dialog: MatDialog) {}

  open () {
    this.dRef = this.dialog.open(this.template);
    this.dRef.afterClosed().subscribe(val => this.result.emit(val));
  }
}

Use it like this:

<button (click)="d.open()">click me</button>
<app-dialog #d (result)="onResult($event)">
  <ng-template>
      <button [mat-dialog-close]="someProperty">leave me alone!</button>
  </ng-template>
</app-dialog>

caveat:
both DialogComponent and MatDialogClose have to be available in the template. A missing directive fails silently, so make sure to reexport MatDialogClose or import MatDialogModule

@benneq Thanks for your solution for a temporary MatDialog implementation!

@j2L4e It looks like that [mat-dialog-close] inside a dialog TemplateRef fails even after exporting MatDialogClose and/or importing MatDialogModule in the closest parent module:

jit_nodeValue_3(...).dialogRef.close is not a function
    at Object.eval [as handleEvent] (TestComponent.html:17)
    at handleEvent (core.js:23009)
    at callWithDebugContext (core.js:24079)
    at Object.debugHandleEvent [as handleEvent] (core.js:23806)
    at dispatchEvent (core.js:20458)
    at core.js:20905
    at HTMLButtonElement.<anonymous> (platform-browser.js:993)

IMO, this is a documentation issue. https://material.angular.io/components/dialog/overview needs an example with TemplateRef using implicit context. This is difficult to figure out if you're not comfortable looking at material2's source code.

The $implicit context is bound to MatDialogConfig.data, which I don't think most people know about. So in this code example, foo, which is arbitrary, is bound to $implicit, which is from MatDialogConfig.data, see _material2/src/lib/dialog/dialog.ts_ _attachDialogContent()

<ng-template #myTemp let-foo>
  <h2 matDialogTitle>My Title</h2>
  <mat-dialog-content>
    <p>{{ foo.myProperty }}</p>
  </mat-dialog-content>
</ng-template>

Then, you need to bind TemplateRef in your component, or just pass it by reference when calling a function from the template.

      <button mat-stroked-button
              (click)="openDialog(myTemp, myData)">
          Open Dialog
      </button>

Component .ts

  openDialog(templateRef: TemplateRef<MyData>, myData: MyData) {
    this.dialog.open(templateRef, { data: myData });
  }

@arlowhite, didn't you get the error:
ERROR Error: No component factory found for undefined. Did you add it to @NgModule.entryComponents?
?

@tonyuifalean Could you show us your code?

If you're facing an unrelated issue, I suggest that you should either try to ask on StackOverflow or open a new issue on this repository.

@EdricChan03: I am using Angular 8, and I am just testing @arlowhite's example.
In my template I have:
image

In the component I have a method, called in ngOnInit:
@ViewChild('myTemp', { static: false })
public myTemp: TemplateRef;
public openMyDialog() {
this.dialog.open(this.myTemp);
}

And in the module I import: MatDialogModule

Could you try moving the public myTemp line to the same line as the ViewChild(...) line? And is there any reason why you're marking _all_ of the methods and properties as public?

@arlowhite Actually, if you look closely in the documentation for TemplateRef, passing in the data interface as the T type parameter will not make any difference than passing in any as TemplateRef is defined to have a C type parameter which is the template's class.

So here's the modified version of your component code:

openDialog(templateRef: TemplateRef<any>, myData: MyData) {
  this.dialog.open(templateRef, { data: myData });
}

Oops, never mind. I did not read the bottom part that mentioned what the C type parameter is for:

The data-binding context of the embedded view, as declared in the聽<ng-template>聽usage.

Could you try moving the public myTemp line to the same line as the ViewChild(...) line? And is there any reason why you're marking _all_ of the methods and properties as public?

Just for testing purposes. In the end I'll try to use a component directly, but thanks for you support!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

LoganDupont picture LoganDupont  路  3Comments

kara picture kara  路  3Comments

theunreal picture theunreal  路  3Comments

Hiblton picture Hiblton  路  3Comments

constantinlucian picture constantinlucian  路  3Comments