Routing to Angular Material Dialogs
By: John Crowson
Tags: Dialog, Angular Material, Angular, Typescript, Router

Option 2: Routing by Path

Add a child path to the parent’s route configuration that opens the dialog.

We will open the dialog when the user navigates to “/home/dialog”.

This option is not as straightforward. Where do we call the MatDialog open from? We cannot arbitrarily execute a function from the Route configuration. We’d need to do it from a component or guard.

// This is NOT possible with the RouterModule...

RouterModule.forRoot([

{

path: 'home',

component: DialogOverviewExample,

children: [

{

path: 'dialog',

executeFunction: openDialog()

}

]

},

{ path: '**', redirectTo: 'home' }

])

We can create a dummy component for the path ‘dialog’ that manages opening, closing, and passing data to the dialog. When the dummy component gets initialized on ‘dialog’ navigation, it will open the dialog.

We’ll call our dummy component DialogEntryComponent :

@Component({

template: ''

})

export class DialogEntryComponent {

constructor(public dialog: MatDialog) {

this.openDialog();

} openDialog(): void {

const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {

width: '250px'

}); dialogRef.afterClosed().subscribe(result => {

console.log('The dialog was closed');

});

}

}

And our simplified DialogOverviewExample :

@Component({

selector: 'dialog-overview-example',

templateUrl: 'dialog-overview-example.html',

styleUrls: ['dialog-overview-example.css']

})

export class DialogOverviewExample {

// MatDialog code moved

// open/close handled in DialogEntryComponent

}

Declare the dummy component and configure the Router Module to load it on ‘dialog’ navigation in main.ts :

imports: [

...

RouterModule.forRoot([

{

path: 'home',

component: DialogOverviewExample,

children: [

{

path: 'dialog',

component: DialogEntryComponent

}

]

},

{ path: '**', redirectTo: 'home' }

])

],

declarations: [

...

DialogEntryComponent

]

In order to provide a launch point for the child route, we’ll need to add a router-outlet to the parent template ( dialog-overview-example.html ):

<ol>

...

</ol>

<router-outlet></router-outlet>

In the same file, remove the existing example functionality that opens the dialog by directly calling the openDialog function. Replace it with a routerLink to the child component:

<button mat-raised-button routerLink="dialog">Pick one</button>

Now our dialog opens when the user navigates to “/home/dialog”, but it does not return to “/home” after the dialog is closed.

Navigate back to the parent in the MatDialog afterClosed subscription:

@Component({

template: ''

})

export class DialogEntryComponent {

constructor(

public dialog: MatDialog,

private router: Router,

private route: ActivatedRoute

) {

this.openDialog();

} openDialog(): void {

const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {

width: '250px'

}); dialogRef.afterClosed().subscribe(result => {

this.router.navigate(['../'], { relativeTo: this.route });

});

}

}

Our app now correctly navigates to and from “/home/dialog”, but the data is not being passed as expected.

How do we pass the data? Our dummy component doesn’t have direct access to the inputted data. As discussed in our introduction, the direct access to inputted data to pass along to the dialog is a benefit of option 1. We can instead pass data using a service (NgRx, local storage, etc). For this example, we’ll create a dummy service:

import { Injectable } from '@angular/core'; @Injectable()

export class DataService {

animal: string;

name: string;

}

We’ll need to add the DataService to the providers array in main.ts .

Inject the DataService in the parent component:

export class DialogOverviewExample {

constructor(public dataService: DataService) { }

}

Update the parent component template to use the DataService:

<ol>

<li>

<mat-form-field>

<input matInput [(ngModel)]="dataService.name" placeholder="What's your name?">

</mat-form-field>

</li>

...

<li *ngIf="dataService.animal">

You chose: <i>{{dataService.animal}}</i>

</li>

</ol>

<router-outlet></router-outlet>

Inject the DataService in the child dialog component. We’ll pass back data to the parent component using the service instead of the MatDialog afterClosed result parameter:

export class DialogOverviewExampleDialog {

animal: string;

name: string;



constructor(

public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,

private dataService: DataService

) {

this.animal = dataService.animal;

this.name = dataService.name;

}



onNoClick(): void {

this.dialogRef.close();

}

}

Update the child dialog template to use the animal and name properties:

<h1 mat-dialog-title>Hi {{name}}</h1>

<div mat-dialog-content>

<p>What's your favorite animal?</p>

<mat-form-field>

<input matInput [(ngModel)]="animal">

</mat-form-field>

</div>

...

We want to pass data back to the parent if the user clicks the “Ok” button and clear the data if user clicks the “No Thanks” button. Update the child dialog template to handle the click instead of passing the animal back using the mat-dialog-close directive:

...

<div mat-dialog-actions>

<button mat-button (click)="onNoClick()">No Thanks</button>

<button mat-button (click)="onOkClick()" cdkFocusInitial>Ok</button>

</div>

Update the DataService animal property from the child dialog component when the user clicks the “Ok” button, clear the DataService animal property when the user clicks the “No Thanks” button:

export class DialogOverviewExampleDialog {

...

onNoClick(): void {

this.dataService.animal = undefined;

this.dialogRef.close();

} onOkClick(): void {

this.dataService.animal = this.animal;

this.dialogRef.close();

}

}

Done!

This option favors applications that maintain state globally and have dialogs that need to exist at multiple route locations (a store checkout dialog at /camera/checkout , /accessories/checkout , etc). After creating a generic DialogEntryComponent, we only need to add the ‘checkout’ child route in the Router Module configuration and add a router-outlet to the parent’s template.

Here’s the final product:

Follow me on Twitter :) @john_crowson

For more Angular goodness, be sure to check out the latest episode of The Angular Show podcast.

EnterpriseNG is coming

EnterpriseNG is a two-day conference from the ng-conf folks coming on November 19th and 20th. Check it out at ng-conf.org