Angular and pure HTML dialogs

No libraries required

Jose I Santa Cruz G
ITNEXT

--

Pure HTML dialogs in a Tauri + Angular app

Have you heard about pure HTML dialogs? While working on the Tauri + Angular app that lead to my article about CSS Dark mode, I noticed that one of the requirements was having a couple of dialog screens, opened over the main app. As I'm not using any external UI libraries besides Tailwind, I relied on native HTML dialogs.

(Just skip to the end for the Angular code)

What's a dialog anyway?

Borrowing the definition from https://design.mindsphere.io/patterns/dialog.html :

A dialog is a window that passes on information to a user and prompts him for a response.

And so simple as that. Think it as a conversational dialog, the other side asks a question or gives us some information, and expects for an answer from us (the user). This answer may be an action, such as clicking a button, and thus starting another operation, or just acknowledging the information and closing the dialog window.

Do we need an external library for dialogs?

Angular Material dialog example

As always, it depends. It depends on how complex is your required dialog, how complex is your application, and mainly, it depends on your will/ability/requirement on building the whole dialog screen. Consider that without a library you'll have to make your own styles for:

  • Dialog window container
  • The titlebar
  • Closing button
  • Backdrop styles (darks screen that "hides" everything behind the dialog window's borders)

And also, without a library you'll have to handle all dialog events or actions.

So, if your requirement demands using a UI library, perhaps implementing your own dialogs may be too demanding. Not saying the "Favorite Animal" dialog example is too complex. And also, haven't seen an UI library that doesn't drag any bloat with it (just see how your bundle size increases just by using Angular Material).

HTML dialogs

Simple pure HTML dialog example from https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog

As you can see from the image and the linked document, HTML already provides a dialog tag, and using it is really simple:

<dialog open>
<p>Greetings, one and all!</p>
<form method="dialog">
<button>OK</button>
</form>
</dialog>

Note the open attribute. It tells our dialog to start in an open state. Dialogs will not be shown unless they are opened.
This usually works, as we want our dialogs to start opened.

Pure HTML dialog with the 'open' attribute

The problem with this kind of dialogs is that they are not modal dialogs, so events meant to be triggered by underlying elements (such as a button beneath the dialog) are still being triggered. See the highlighted Click + event button. And also, we don't have a by default backdrop for the dialog window.

For modal dialogs, what we really need is a modal dialog 😅

Pure HTML modal dialogs

Taking many thoughts from https://www.bennadel.com/blog/3620-most-of-your-modal-windows-should-be-directly-accessible-by-route-in-angular-7-2-15.htm , I will implement our modal dialogs in a different route. In Angular it is as simple as defining a new component and a new route for it, in a certain way it may help the SoC (Separation of concerns) of our app. We'll get to the Angular code later.

<dialog id="my-dialog">
<p>Greetings, one and all!</p>
<form method="dialog">
<button onclick="closeDialog()">Close dialog</button>
</form>
</dialog>

The open attribute is not included in the dialog tag, and this is because we will open and close the dialog using javascript (still no Angular here).

const myDialog = document.querySelector('#my-dialog');
myDialog.showModal();

function closeDialog() {
myDialog.close();
}

In a codepen:

Simple pure HTML modal dialog example (https://codepen.io/jsantacl/pen/VwEMNOE)

But even though the dialog behaves like a modal, it doesn't really look like a modal.

How do we style the modal dialog's backdrop?

The modal dialog's backdrop is not a tag you include explicitly on your HTML code. It's a pseudo-element that's shown when the showModal() function is called.

dialog::backdrop {
background: rgba(0,0,0,0.6);
backdrop-filter: blur(3px);
}
Backdrop styles applied

The CSS code is commented on the pen, just uncomment it and try it.
It will use a semi-transparent black background (60% opacity) and apply a blur effect for everything behind the backdrop.

The really nice thing about pure HTML modal dialogs is that you don't have to do anything (besides applying a nice style to the backdrop) in order to show the dialog centered in the screen, no extra styles, no vertical centering tricks, it "just works".

and finally…

Angular pure HTML modal dialogs

I guess you already have the main idea, HTML for the template, some CSS, and calling the showModal() function. As our dialog will "live" in a separate route, there's no real need to handle the close the dialog event (if you need it, you know, just call the close() function).

I'll be using Tailwind for the CSS styles, MHO a bloated HTML is better than bloating the libraries.

The template:

<dialog #appDialog class="border border-primary bg-secondary text-primary w-fit max-w-screen-sm">
<h1 class="text-2xl font-semibold w-100 border-b border-slate-300 px-2 mb-4">{{dialogTitle}}</h1>
<ng-content></ng-content>
</dialog>

I didn't design a titlebar for this kind of dialogs, just placed a h1 title tag. You can skip the title and use only the projected content (ng-content tag), the beauty of open source is that you can adapt the code to your fits.

The SCSS (styles):

:host {
dialog::backdrop {
@apply bg-neutral-900/80 backdrop-blur-sm;
}
}

Somewhat the same styles I used in the codepen, but styled ala Tailwind.

The component:

import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild, inject } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
selector: 'app-dialog',
standalone: true,
imports: [CommonModule],
templateUrl: './dialog.component.html',
styleUrls: ['./dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DialogComponent implements OnInit {
@Input() dialogTitle!: string;
@ViewChild('appDialog', { static: true }) dialog!: ElementRef<HTMLDialogElement>;
cdr = inject(ChangeDetectorRef);

ngOnInit(): void {
this.dialog.nativeElement.showModal();
this.cdr.detectChanges();
}

ngOnDestroy(): void {
this.dialog.nativeElement.close();
this.cdr.detectChanges();
}

}

The code DOES handle closing the dialog (for covering other use cases).

Use example:

The app.routes.ts file:

import { Route } from '@angular/router';

export const appRoutes: Route[] = [
{
path: 'my-dialog',
loadComponent: () => import('./pages/my-dialog.component').then(m => m.MyDialogComponent)
},
{
path: '*',
redirectTo: '/'
}
];

The ./pages/my-dialog.component.ts file:

import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DialogComponent } from '../components/dialog/dialog.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';

@Component({
selector: 'app-my-dialog',
standalone: true,
imports: [
CommonModule,
DialogComponent
],
templateUrl: './my-dialog.component.html',
})
export class MyDialogComponent {
router = inject(Router);

closeDialog() {
// Navigate to / to close the dialog
// ... you can do many other things here
this.router.navigate(['/']);
}
}

Remember to import the component where ever you want to use it (it’s a standalone component).

And the component's ./my-dialog.component.html file:

<app-dialog dialogTitle="Greetings, one and all!">
<form>
<button (click)="closeDialog()">Close dialog</button>
</form>
</app-dialog>

We can argue if not using a dialogs library is a real choice. As said, it depends, it may work for some and not work for others. The main idea of this article was showing that plain HTML already has many things some libraries aim to provide.

Thanks for reading so far, hope you enjoyed this article 👍🏻

--

--

Writer for

Polyglot senior software engineer, amateur guitar & bass player, geek, husband and dog father. Not precisely in that order.