Modal Dialog Route in Angular

The other day I was working on a pet project in Angular.

One of the things I wanted to achieve was to have a route which will show a popup. In simple words the case looks the following: the user is on a main page https://pet-project.com/, then navigates to https://pet-project.com/create through a link etc. and gets a popup dialog on top of a main page.

Showing one page on top of the other can be simply done through child routes (make sure your parent view has a router-outlet to show child components):

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FeedComponent } from './feed/feed.component';
import { DialogComponent } from './dialog/dialog.component';

const routes: Routes = [
  {
    path: '',
    component: FeedComponent,
    children: [
      { path: 'create', component: DialogComponent },
    ]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Where the DialogComponent will spin up a CreateComponent and navigate back once closed:

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { CreateComponent } from './create/create.component';

@Component({ template: '' })
export class DialogComponent implements OnInit {
  constructor(private dialog: MatDialog) { }

  ngOnInit() {
    const dialogRef = this.dialog.open(CreateComponent, {});
    dialogRef.afterClosed().subscribe(_ => this.router.navigate(['..'], { relativeTo: this.route }));
  }

So linking modals to routes was pretty easy to achieve.

Things got more interesting when I tried to generalize the DialogComponent to work with any child component. Coming from a C# world, I thought it will be a piece of cake with generics, just by using DialogComponent<ChildComponent>.

After updating the route to something like this:

{ path: 'create', component: DialogComponent<CreateComponent> }

But I got an error:

Value of type 'typeof DialogComponent' is not callable. Did you mean to include 'new'?

Investigating it a bit led me to InjectionToken. I will skip the details as it is not a part of the final solution.

The next one was making the DialogComponent generic then calling

this.dialog.open(T, {});

And because generics in TypeScript are compile-time only, this did not work either, throwing the other error at me:

'T' only refers to a type, but is being used as a value here.

Fighting with the issue for a few hours did not push me much towards a solution.

So I took a step back and decided to use parameterized routes instead of generics. So we would pass a child component as a route parameter:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FeedComponent } from './feed/feed.component';
import { UploadComponent } from './upload/upload.component';
import { DialogComponent } from './dialog/dialog.component';

const routes: Routes = [
  {
    path: '',
    component: FeedComponent,
    children: [
      { path: 'upload', component: DialogComponent, data: { component: UploadComponent } }
    ]
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

And the final version of DialogComponent will be like this:

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, Data } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';

@Component({ template: '' })
export class DialogComponent implements OnInit {
  constructor(
    private dialog: MatDialog,
    private router: Router,
    private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.data.subscribe(data => this.openDialog(data));
  }

  openDialog(data: Data): void {
    const dialogRef = this.dialog.open(data.component, {});
    dialogRef.afterClosed().subscribe(_ => this.router.navigate(['..'], { relativeTo: this.route }));
  }
}

Hope this will save you few hours of a hustle.

Happy routing!

Comments