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
Post a Comment