Make Dialogs Using the Angular Router with Named Router Outlets
Hi guys, today I’m going to show you how you can use Angular Named Router Outlets feature to achieve the Dialog Pattern very easily.

Dialogs are very widespread, despite being such an unfrienldy encounter: we all came across annoying popups that ask for confirmation about some cookie related stuff while surfing on the Internet.
But not all dialogs are made equal, some dialogs, simply take a moment to gather information from the user when some action need to take precedence over all the rest.
Dialogs can usually be avoided when you design your app freely without projectual constraints, yet when you work as a consultant, as I do, requirements are such that you are required to use dialogs.
Our Objective
What we want is to be able to open a dialog and wait for the user to take action, read the result of said user action and close the dialog.
In particular we want to implement the Are You Sure Dialog with a title, a message and a couple of buttons, one to dismiss and one to confirm.
To do that we want to use the Angular router, and configure the router in such a way that we can open the dialog without disturbing the primary outlet, for that we need Named Router Outlet.
The repository with the source code for this can be found at https://github.com/4skinSkywalker/dialogs-with-router
The demo can be found at https://4skinskywalker.github.io/dialogs-with-router/
Configuring the Router
We will make use of a feature called Named Router Outlet that let you have separate navigations in the same view, for instance you can have a primary navigation for your sidebar and a separate secondary navigation for your main content, and controls them indipendently!
In our case we will use Named Router Outlet to open the modal without disturbing the primary navigation of our application.
In order to use an auxilary router outlet we define a new router-outlet element and use its “name” property as follows:
<router-outlet name="auxiliary"></router-outlet>
We can put the above where we want, I put it in my AppComponent.
In case you are wondering “auxilary” is just a name of choice, feel free to choose what you prefer, but remember what you’ve used as we need it later.
After that, we must configure the router via route array in our routing module, as follows:
const routes: Routes = [
{
path: 'dialog-are-you-sure',
component: DialogAreYouSureComponent,
outlet: 'auxiliary'
}
];
The above array describes the path to open our dialogs, the outlet property is used to tell the router to put the dialog in the router-outlet with the name “auxilary” that we defined before.
To open the dialog we will use the following code:
this.router.navigate to ['', { outlets: { auxiliary: [ 'dialog-are-you-sure' ] } }];
To close the dialog we simply pass null as “auxilary” value, like so:
this.router.navigate([{ outlets: { auxiliary: null } }]);
Go to Creating DialogService to Open/Close Dialogs to see where and in which context these two lines of code are used.
Our Super Basic Folder Structure
We create a folder named dialogs, which will hold all our dialog components (these are regular Angular components):

You’ll have to create DialogService which will be at the root of the dialog folder, and each dialog component will have its dedicated folder.
Creating the Base Dialog
We start with the dialog-base component which is the scaffold for all our dialogs:
<div class="card-wrap" (click)="dismiss($event)">
<div class="card">
<div class="card-header">
<ng-content select="[header]"></ng-content>
</div>
<div class="card-body">
<ng-content select="[body]"></ng-content>
</div>
<div class="card-footer">
<ng-content select="[footer]"></ng-content>
</div>
</div>
</div>
The above component uses Content Projection to make so that consumers (which are other dialogs) can inject custom markup.
Let’s define some CSS and make so that card-wrap takes all the page with a mild blackish appearance (sort of a backdrop) and the card is positioned in the middle of the screen with well defined areas such as header, body and footer:
.card-wrap {
background-color: #0002;
display: grid;
height: 100vh;
left: 0;
place-items: center;
position: fixed;
top: 0;
width: 100vw;
}
.card {
background-color: #fff;
border-radius: 0.5rem;
box-shadow: 0 0 8px 4px #0002;
width: clamp(25ch, 50%, 50ch);
}
.card-header {
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
border-bottom: 1px solid #eee;
padding: 0.5rem;
}
.card-body {
padding: 0.5rem;
max-height: 400px;
overflow-y: auto;
}
.card-footer {
border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
border-top: 1px solid #eee;
padding: 0.5rem;
}

Creating Are You Sure Dialog with Content Projection
Now, we can make use of Content Projection to reuse dialog-base and make our dialog-are-you-sure component:
<app-dialog-base>
<ng-container header>Are You Sure?</ng-container>
<ng-container body>Are You Sure dialog is working</ng-container>
<ng-container footer>1234</ng-container>
</app-dialog-base>

For now let’s move on, we will add functionalities and pass some data to our Are You Sure Dialog later…
Creating DialogService to Open/Close Dialogs
Now that we are set up with our component we need to make DialogService, which will be injected in dialog consumers to open/close dialogs.
I want my dialogs to take input data and return a promise of data, so that the consumer can await until either the dialog is dismissed or closed with data.
Let’s create a new DialogService and put some juicy code in it:
export class DialogService {
data: any;
private resolve: Function | undefined;
private reject: Function | undefined;
constructor(
private router: Router
) { }
open(dialogPath: string, data: any) {
this.data = data;
this.router.navigate(['', { outlets: { auxiliary: [ dialogPath ] } }]);
return new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
})
}
close(data: any) {
if (!this.resolve) return;
this.router.navigate([{ outlets: { auxiliary: null } }]);
this.resolve(data);
}
dismiss(error = 'Modal dismissed.') {
if (!this.reject) return;
this.router.navigate([{ outlets: { auxiliary: null } }]);
this.reject(error);
}
}
This is the very engine of the machine.
We have a public property that holds the input data for our dialog, it’s set at the time the consumer calls the open method.
There are two private properties: resolve and reject.
Resolve and reject are extracted from the new Promise returned by the open method call, resolve is used to return output data to the consumer that’s awaiting the promise while reject is used to dismiss the dialog with an error.
The three methods are self descripting, they are used to open, close and dismiss.
As already mentioned the whole dialog instance is handled with a promise.
Upgrading our Are You Sure Dialog
We come back to the Are You Sure Dialog, passing data and adding functionalities to it.
We want to be able to be able to pass a title and a message, and either dismiss or close the dialog.
To pass data and give functionalities to Are You Sure we need to inject DialogService in it:
export class DialogAreYouSureComponent {
constructor(
public ds: DialogService
) { }
confirm() {
this.ds.close(true);
}
}
We created a confirm method that delegates to the close method of DialogService passing the boolean value true to it.
DialogService is publicly availiable so that the template can access its property data and the method dismiss.
Inside the template we can show data via interpolation:
<app-dialog-base *ngIf="ds.data">
<ng-container header>{{ ds.data.title }}</ng-container>
<ng-container body>{{ ds.data.message }}</ng-container>
<ng-container footer>
<div class="footer-buttons">
<button (click)="ds.dismiss()">Dismiss</button>
<button (click)="confirm()">Confirm</button>
</div>
</ng-container>
</app-dialog-base>
Consuming Are You Sure Dialog
Now that everything is set up let’s open our first dialog!
Inside the template of the consumer create a button and bind a function to open the dialog to the click event:
<button (click)="openDialogAreYouSure()">Open Dialog Are You Sure</button>
Inside the component of the consumer let’s make a function to open Are You Sure Dialog with a title and a message:
export class AppComponent {
constructor(
private ds: DialogService
) { }
async openDialogAreYouSure() {
try {
const result = await this.ds.open('dialog-are-you-sure', {
title: 'Confirmation dialog',
message: 'Are you sure you want to continue?'
});
console.log(result);
}
catch (err) {
console.error(err);
}
}
}
As you can see we marked openDialogAreYouSure with the keyword async, so that we can await the Promise returned by DialogService.
We use try/catch to log into the console whether the user has confirmed or dismissed the dialog.
The End
Thank you a lot for reading this Medium article, I really hope you liked it!
If you have any question feel free to post it here or send an issue on GitHub.
The repository with the source code for this can be found at https://github.com/4skinSkywalker/dialogs-with-router
The demo can be found at https://4skinskywalker.github.io/dialogs-with-router/
Fredo Corleone