harrewijnen.net

Simple dialogs with AngularJS and TypeScript

While working an Angular (1) web app I ran into the age old problem of showing the user notifications when certain conditions are met. Show them a little pop-up, in simple terms.

So what does it take to show a popup? We know the dialog element exists but cannot be used in this case due to browser requirements. The easiest way I could think of is adding a fixed positioned div the the user on top of all other content. Add some CSS in there to make it look nice and we have dialogs.

There are a number of ways this can be achieved in Angular and for my case I chose a service. So what does this service look like? Please note that we are using TypeScript, which is like javascript, but not actual javascript. It makes javascript better.

Let's start by adding a class that represents the service:

/// <reference path="..." />

module DemoApp.Services {
	export class DialogService {
		constructor() { }
	}
}

Which we can register as a service in the root of our app.

/// <reference path="..." />

var app = angular.module("demoApp", [""]);
<snip>Other stuff here</snip>

app.service("dialogService", DemoApp.Services.DialogService);

We add a service to our app called dialogService and pass its constructor function along with it. Angular now knows about our dialog service and we can inject it where ever we want.

Except... Our service doesn't do anything yet!

Let's add a function that creates a dialog similar to an alert(...);. Just some text and a button to close the dialog. Add the following function to the DialogService class.

showModalMessage = (title: string, text: string, onClose: () => void):void => { }

The dialog needs a title, some text and to let us respond to the user closing the dialog we have a callback function onClose. The function and callback both return void, they have no return values.

Now for the code that creates a the elements that make up our fancy new dialog. Let's dive right in.

var body = document.body;

var dialogElement = document.createElement("div");
dialogElement.classList.add("dialog");

var dialogContent = document.createElement("div");
dialogContent.classList.add("dialog-content");

var titleElement = document.createElement("h1");
titleElement.innerHTML = title;
dialogContent.appendChild(titleElement);

var textElement = document.createElement("p");
textElement.innerHTML = text;
dialogContent.appendChild(textElement);

var okButton = document.createElement("button");
okButton.innerHTML = "OK";

var typeAttr = document.createAttribute("type");
typeAttr.value = "button";

okButton.attributes.setNamedItem(typeAttr);

dialogContent.appendChild(okButton);

dialogElement.appendChild(dialogContent);

var onOkClicked = () => {
	okButton.removeEventListener("click", onOkClicked);
	body.removeChild(dialogElement);

	onClose();
}

okButton.addEventListener("click", onOkClicked);

body.appendChild(dialogElement);

From top to bottom. We generate two div elements, one as a container and one for the dialog content. Add in a header with an h1 element and a p for the content. Create a button that calls the callback function, cleans up the eventlistener and removes the generated elements when clicked. Finally we add the whole lot to the body element to get it rendered.

To make it behave like a dialog, we also need some CSS. You can style this anyway you want but to get you started I have some ready-made LESS that you can use (or maybe not, then I'm sorry for using LESS instead of CSS).

.dialog {
	position: fixed;
	width: 100%;
	min-height: 200px;
	top: 15%;
	background-color: rgb(0, 148, 255);
	z-index: 1;

	.dialog-content {
		position: relative;
		max-width: 500px;
		padding: 10px;
		margin: 0 auto;
		z-index: 2;
		color: #fff;
		text-align: center;

		h1,
		p {
			padding: 0;
			margin: 0 0 10px 0;
		}

		button {
			margin-top: 20px;
		}
	}

	&:after {
		content: "";
		position: fixed;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		background-color: rgba(128, 128, 128, 0.5);
		z-index: -1;
	}
}

Notice the position: fixed; to lock it into place and ignore any scrolling that will happen. There is no escaping this message. The other noteworthy thing is the :after styling. This renders a semi-transparent overlay behind the dialog we are showing to make sure the user cannot interact with anything else on the page.

Using the styles above should give you something like this:

Dialog

Actually using the dialogService requires that you inject it into other Angular modules (a controller in this case) like this:

module DemoApp.Controllers {

	export class DemoController {

		dialogService: RegistrationApp.Services.DialogService;

		static $inject = ["dialogService"]
		constructor(dialogService: DemoApp.Services.DialogService) {
			this.dialogService = dialogService;
		}

		doSomeWork = () => {
			// Important stuff happens here...

			this.dialogService.showModalMessage("Test", "This is a testmessage!", () => { });
		}
	}
}

The callback is just an empty function in the example but you can pass in any function that doesn't take any arguments and returns void.

We now have a single reusable source of dialogs that can be used anywhere in your angular app! Enjoy!

Posted on
Tagged Javascript, Angular, HTML, CSS, itq