January 24 2017

Angular 2/4 - Custom Modal Window / Dialog Box

May 9 2017 - Updated to Angular 4.1.1 for both Webpack and SystemJS versions.

In this tutorial we'll cover how to implement modal windows (dialog boxes) in Angular 2/4 with TypeScript. The example is a custom modal without the need for any 3rd party libraries.

Tutorial Code on GitHub

The tutorial code is available on GitHub at https://github.com/cornflourblue/angular2-custom-modal
A webpack version of the tutorial code is also available at https://github.com/cornflourblue/angular2-custom-modal-webpack.

There are plenty of 3rd party libraries and plugins out there that include modal windows, up until recently I used them myself when I needed to add a modal to a project. The main issue that I have with 3rd party libraries is that they usually contain a lot of features I don't need which adds unnecessary bloat to my application, so last year I took some time and created a custom modal window in Angular 1 to see how difficult it would be and also to remove the mystery I had in my mind about exactly how modals work.

Once I'd finished I was surprised at the relatively small amount of code required to implement a custom modal window, most of the modal 'magic' is handled by a handful of css styles (see the modal.less file) while TypeScript is used for showing and hiding the modal windows.

Here it is in action: (See on Plunker at http://plnkr.co/edit/gPCTvV?p=preview)

Update History:

  • 09 May 2017 - Updated to Angular 4.1.1 for both Webpack and SystemJS versions
  • 24 Jan 2017 - Built with Angular 2.2.1


Running the Angular 2/4 Modal Example Locally

  1. Install NodeJS (> v4) and NPM (> v3) from https://nodejs.org/en/download/, you can check the versions you have installed by running node -v and npm -v from the command line.
     
  2. Download the project source code from https://github.com/cornflourblue/angular2-custom-modal
     
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
     
  4. Start the application by running npm start from the command line in the project root folder.


Running the Webpack Version of the Angular 2/4 Modal Example Locally

This version of the example uses Webpack to bundle the angular 2 modules together and perform other build tasks, the structure is based on the Angular 2 Webpack Introduction on the Angular 2 docs site.

The Webpack Dev Server is used as the local web server for this version.

  1. Install NodeJS (> v4) and NPM (> v3) from https://nodejs.org/en/download/, you can check the versions you have installed by running node -v and npm -v from the command line.
     
  2. Download the project source code from https://github.com/cornflourblue/angular2-custom-modal-webpack
     
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
     
  4. Start the application by running npm start from the command line in the project root folder.
     
  5. Browse to http://localhost:8080 to test your application.


Angular 2/4 Custom Modal Setup

There are three main pieces of code that make up this custom modal implementation:

  • Modal LESS/CSS styles in app/_content/modal.less
  • Angular 2 Modal Service in app/_services/modal.service.ts
  • Angular 2 Modal Directive in app/_directives/modal.component.ts


Adding an Angular 2/4 Modal to your page

Once you have the above three files (or the code from them) integrated into your project, you can add modals to your pages with the custom <modal> tag like this:

<modal id="custom-modal-1">
    <div class="modal">
        <div class="modal-body">
            <h1>A Custom Modal!</h1>
            <p>
                Home page text: <input type="text" [(ngModel)]="bodyText" />
            </p>
            <button (click)="closeModal('custom-modal-1');">Close</button>
        </div>
    </div>
    <div class="modal-background"></div>
</modal>

A unique id is required for each modal on a page, this is passed to the modal service from your controller to identify which modal you want to open/close.

The modal, modal-body and modal-background divs are required, but everything inside the modal-body can be changed. You could also update the modal LESS/CSS if you want to change the styles of the modals, e.g to make them smaller or add CSS transitions etc.


Opening & Closing Angular 2/4 Modals

To open and close (show/hide) a modal dialog you need to use an instance of the ModalService by importing it into your controller, injecting it into the constructor and calling this.modalService.open() and this.modalService.close(), e.g:

import { Component, OnInit } from '@angular/core';

import { ModalService } from '../_services/index';

@Component({
    moduleId: module.id,
    templateUrl: 'home.component.html'
})

export class HomeComponent implements OnInit {
    private bodyText: string;

    constructor(private modalService: ModalService) {
    }

    ngOnInit() {
        this.bodyText = 'This text can be updated in modal 1';
    }

    openModal(id: string){
        this.modalService.open(id);
    }

    closeModal(id: string){
        this.modalService.close(id);
    }
}

The example controller passes the modal id from the view, but it could also be set directly in the controller like this:

this.modalService.open('my-custom-modal');


The view then calls the methods exposed by the controller in the usual way:

<div class="col-md-6 col-md-offset-3">
    <h1>Home</h1>
    <p>{{bodyText}}</p>
    <button (click)="openModal('custom-modal-1')">Open Modal 1</button>
    <button (click)="openModal('custom-modal-2')">Open Modal 2</button>
</div>

 

Breakdown of the Angular 2/4 Custom Modal Code

Below is a breakdown of the pieces of code used to implement the custom modal example in Angular 2, you don't need to know the details of how it all works to use the modals in your project, it's only if you're interested in the nuts and bolts or if you want to modify the code or behaviour.


LESS/CSS Styles for Angular 2/4 Modal

These are the styles applied to the custom modals in this example, however they could also be used in non-angular2 projects as it's just pure LESS/CSS.

/* MODAL STYLES
-------------------------------*/
modal {
    /* modals are hidden by default */
    display: none;
    .modal {
        /* modal container fixed across whole screen */
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        /* z-index must be higher than .modal-background */
        z-index: 1000;
        
        /* enables scrolling for tall modals */
        overflow: auto;
        .modal-body {
            padding: 20px;
            background: #fff;
            /* margin exposes part of the modal background */
            margin: 40px;
        }
    }
    .modal-background {
        /* modal background fixed across whole screen */
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        /* semi-transparent black  */
        background-color: #000;
        opacity: 0.75;
        
        /* z-index must be below .modal and above everything else  */
        z-index: 900;
    }
}
body.modal-open {
    /* body overflow is hidden to hide main scrollbar when modal window is open */
    overflow: hidden;
}


Angular 2/4 Modal Service

The modal service manages the communication that's required between angular 2 controllers and modal directive instances. It maintains a current list of available modals on the page exposes methods for interacting with those modals.

import * as _ from 'underscore';

export class ModalService {
    private modals: any[] = [];

    add(modal: any) {
        // add modal to array of active modals
        this.modals.push(modal);
    }

    remove(id: string) {
        // remove modal from array of active modals
        let modalToRemove = _.findWhere(this.modals, { id: id });
        this.modals = _.without(this.modals, modalToRemove);
    }

    open(id: string) {
        // open modal specified by id
        let modal = _.findWhere(this.modals, { id: id });
        modal.open();
    }

    close(id: string) {
        // close modal specified by id
        let modal = _.find(this.modals, { id: id });
        modal.close();
    }
}


Angular 2/4 Modal Directive Component

The custom modal directive is used for adding modals anywhere in an angular application by using the <modal> tag. Each modal instance registers itself with the ModalService when it loads in order for the service to be able to open and close modal windows, and removes itself from the ModalService when it's destroyed using the ngOnDestroy event.

import { Component, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';
import * as $ from 'jquery';

import { ModalService } from '../_services/index';

@Component({
    moduleId: module.id.toString(),
    selector: 'modal',
    template: '<ng-content></ng-content>'
})

export class ModalComponent implements OnInit, OnDestroy {
    @Input() id: string;
    private element: JQuery;

    constructor(private modalService: ModalService, private el: ElementRef) {
        this.element = $(el.nativeElement);
    }

    ngOnInit(): void {
        let modal = this;

        // ensure id attribute exists
        if (!this.id) {
            console.error('modal must have an id');
            return;
        }

        // move element to bottom of page (just before </body>) so it can be displayed above everything else
        this.element.appendTo('body');

        // close modal on background click
        this.element.on('click', function (e: any) {
            var target = $(e.target);
            if (!target.closest('.modal-body').length) {
                modal.close();
            }
        });

        // add self (this modal instance) to the modal service so it's accessible from controllers
        this.modalService.add(this);
    }

    // remove self from modal service when directive is destroyed
    ngOnDestroy(): void {
        this.modalService.remove(this.id);
        this.element.remove();
    }

    // open modal
    open(): void {
        this.element.show();
        $('body').addClass('modal-open');
    }

    // close modal
    close(): void {
        this.element.hide();
        $('body').removeClass('modal-open');
    }
}

 

Web Developer Sydney

Feel free to drop me a line if you're looking for web developer or Angular 2+ developer in Sydney Australia, I also provide remote contracting services for clients outside Sydney.


Sponsored by