July 13 2016

AngularJS - Custom Modal Example & Tutorial

In this post I'll show how to build your own custom modal popups in AngularJS without the need for 3rd party libraries such as UI Bootstrap.

The project is available on GitHub at https://github.com/cornflourblue/angularjs-custom-modal-example.

In the past I've always used plugins for implementing modals but decided to have a crack at it myself recently, one of the reasons was that I wanted my modal window to share the same controller and viewmodel ($scope) as the page it was in. I didn't want to have to create a separate controller for each modal that would then need to send data to the parent page controller via some other means, I wanted to keep it as simple as possible.

Another reason I wanted to create a custom modal was to make my app as lean as possible by removing the extra bloat added by unnecessary (and sometimes large) third party libraries.

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

 

AngularJS Custom Modal Setup

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

  • Modal LESS/CSS styles in _/content/modal.less
  • Angular Modal Service in _services/modal.service.js
  • Angular Modal Directive in _directives/modal.directive.js


Adding an AngularJS Custom 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" ng-model="vm.bodyText" />
            </p>
            <button ng-click="vm.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 AngularJS Custom Modals

To open and close (show/hide) modal windows you need to use the ModalService by injecting it into your controller and calling the ModalService.Open() and ModalService.Close() methods, e.g:

(function () {
    'use strict';

    angular
        .module('app')
        .controller('Home.IndexController', Controller);

    function Controller(ModalService) {
        var vm = this;

        vm.openModal = openModal;
        vm.closeModal = closeModal;

        initController();

        function initController() {
            vm.bodyText = 'This text can be updated in modal 1';
        }
        
        function openModal(id){
            ModalService.Open(id);
        }

        function closeModal(id){
            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:

ModalService.Open('my-custom-modal');


The view then calls the functions exposed by the viewmodel in the usual way:

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

 

Breakdown of the AngularJS Custom Modal Code

Below is a breakdown of the pieces of code used to implement the custom modal example, 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 Custom Angular Modal

These are the styles applied to the custom modals in this example, however they could also be used in non-angular 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;
}


AngularJS Custom Modal Service

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

(function () {
    'use strict';

    angular
        .module('app')
        .factory('ModalService', Service);

    function Service() {
        var modals = []; // array of modals on the page
        var service = {};

        service.Add = Add;
        service.Remove = Remove;
        service.Open = Open;
        service.Close = Close;

        return service;

        function Add(modal) {
            // add modal to array of active modals
            modals.push(modal);
        }
        
        function Remove(id) {
            // remove modal from array of active modals
            var modalToRemove = _.findWhere(modals, { id: id });
            modals = _.without(modals, modalToRemove);
        }

        function Open(id) {
            // open modal specified by id
            var modal = _.findWhere(modals, { id: id });
            modal.open();
        }

        function Close(id) {
            // close modal specified by id
            var modal = _.findWhere(modals, { id: id });
            modal.close();
        }
    }

})();


AngularJS Custom Modal Directive

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 scope '$destroy' event.

(function () {
    'use strict';

    angular
        .module('app')
        .directive('modal', Directive);

    function Directive(ModalService) {
        return {
            link: function (scope, element, attrs) {
                // ensure id attribute exists
                if (!attrs.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
                element.appendTo('body');

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

                // add self (this modal instance) to the modal service so it's accessible from controllers
                var modal = {
                    id: attrs.id,
                    open: Open,
                    close: Close
                };
                ModalService.Add(modal);
            
                // remove self from modal service when directive is destroyed
                scope.$on('$destroy', function() {
                    ModalService.Remove(attrs.id);
                    element.remove();
                });                

                // open modal
                function Open() {
                    element.show();
                    $('body').addClass('modal-open');
                }

                // close modal
                function Close() {
                    element.hide();
                    $('body').removeClass('modal-open');
                }
            }
        };
    }
})();

 

Web Development and AngularJS Consultant Sydney

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


Sponsored by