Published: April 16 2019

Angular 7 - Custom Modal Window / Dialog Box

Built with Angular 7.2.13 and Webpack 4.8.1

Other versions available:

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

Tutorial example is available on GitHub at https://github.com/cornflourblue/angular-7-custom-modal

There are plenty of plugins and libraries out there that include modal windows, in the past I used them myself when I needed to add a modal to a new project. The main issue that I have with 3rd party plugins is that they usually contain a lot of features I don't need which adds unnecessary bloat to my application, so a few years ago I took the time to create 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.

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

Here it is in action: (See on StackBlitz at https://stackblitz.com/edit/angular-7-custom-modal-dialog)


Running the Angular 7 Modal Dialog Locally

  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the project source code from https://github.com/cornflourblue/angular-7-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.


Angular 7 Custom Modal Setup

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

  • Modal LESS/CSS styles in /src/app/_content/modal.less
  • Angular 7 Modal Service in /src/app/_services/modal.service.ts
  • Angular 7 Modal Component in /src/app/_components/modal.component.ts


Adding an Angular 7 Modal Dialog 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 <jw-modal> tag like this:


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

IMPORTANT: A unique id is required for each modal on a page, this is passed to the modal service from your controller so it knows which modal to open/close.

You can put any content you'd like inside the <jw-modal> element, 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. By default modals are closed on background click, to disable this remove the chunk of code in modal.component.ts located directly below the comment // close modal on background click.


Opening & Closing Angular 7 Modal Dialogs

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';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent implements OnInit {
    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 home controller above 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 home view then calls the methods exposed by the home controller in the usual way:

<div>
    <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 7 Custom Modal Code

Below is a breakdown of the pieces of code used to implement custom modal dialogs in Angular 7 & TypeScript, 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 underlying code or behaviour.


LESS/CSS Styles for Angular 7 Modal Dialogs

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

I prefixed the modal element and classes with jw- to prevent conflicts with 3rd party css libraries such as Bootstrap.

/* MODAL STYLES
-------------------------------*/
jw-modal {
    /* modals are hidden by default */
    display: none;

    .jw-modal {
        /* modal container fixed across whole screen */
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;

        /* z-index must be higher than .jw-modal-background */
        z-index: 1000;
        
        /* enables scrolling for tall modals */
        overflow: auto;

        .jw-modal-body {
            padding: 20px;
            background: #fff;

            /* margin exposes part of the modal background */
            margin: 40px;
        }
    }

    .jw-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 .jw-modal and above everything else  */
        z-index: 900;
    }
}

body.jw-modal-open {
    /* body overflow is hidden to hide main scrollbar when modal window is open */
    overflow: hidden;
}


Angular 7 Modal Service

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

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
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
        this.modals = this.modals.filter(x => x.id !== id);
    }

    open(id: string) {
        // open modal specified by id
        let modal: any = this.modals.filter(x => x.id === id)[0];
        modal.open();
    }

    close(id: string) {
        // close modal specified by id
        let modal: any = this.modals.filter(x => x.id === id)[0];
        modal.close();
    }
}


Angular 7 Modal Component

The custom modal component is used to add modal windows anywhere in your angular application by using the <jw-modal> tag. Each modal instance registers itself with the ModalService when it loads using the ngOnInit lifecycle hook, and removes itself from the ModalService when it is destroyed using the ngOnDestroy lifecycle hook.

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

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

@Component({
    selector: 'jw-modal',
    template: 
        `<div class="jw-modal">
            <div class="jw-modal-body">
                <ng-content></ng-content>
            </div>
        </div>
        <div class="jw-modal-background"></div>`
})
export class ModalComponent implements OnInit, OnDestroy {
    @Input() id: string;
    private element: any;

    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
        document.body.appendChild(this.element);

        // close modal on background click
        this.element.addEventListener('click', function (e: any) {
            if (e.target.className === 'jw-modal') {
                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 component is destroyed
    ngOnDestroy(): void {
        this.modalService.remove(this.id);
        this.element.remove();
    }

    // open modal
    open(): void {
        this.element.style.display = 'block';
        document.body.classList.add('jw-modal-open');
    }

    // close modal
    close(): void {
        this.element.style.display = 'none';
        document.body.classList.remove('jw-modal-open');
    }
}

 


Need Some Angular 7 Help?

Search fiverr for freelance Angular 7 developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by