<!DOCTYPE html>
<html ng-app="app">
<head>
    <title>AngularJS Custom Modal Example & Tutorial</title>

    <!-- application less -->
    <link href="_content/modal.less" rel="stylesheet/less" type="text/css" />
    <link href="_content/app.less" rel="stylesheet/less" type="text/css" />

    <script src="//cdnjs.cloudflare.com/ajax/libs/less.js/2.5.3/less.min.js"></script>
</head>
<body>
    <!-- main app container -->
    <div>
        <nav>
            <a href="#/">Home</a>
            <a href="#/test-page">Test Page</a>
        </nav>
        <ui-view></ui-view>
    </div>

    <!-- credits -->
    <div class="credits">
        <p>
            <a href="http://jasonwatmore.com/post/2016/07/13/AngularJS-Custom-Modal-Example-Tutorial.aspx" target="_top">AngularJS Custom Modal Example & Tutorial</a>
        </p>
        <p>
            <a href="http://jasonwatmore.com" target="_top">JasonWatmore.com</a>
        </p>
    </div>

    <!-- jquery -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>

    <!-- underscore -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

    <!-- angular -->
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>

    <!-- app -->
    <script src="app.js"></script>
    <script src="home/index.controller.js"></script>
    <script src="test-page/index.controller.js"></script>
    <script src="_directives/modal.directive.js"></script>
    <script src="_services/modal.service.js"></script>
</body>
</html>
/* EXAMPLE STYLES
-------------------------------*/
body {
    font-family: roboto;
    padding: 20px;
}

nav {
    margin-bottom: 20px;
    padding-bottom: 20px;
    border-bottom: 1px solid #ddd;
    
    a {
        margin-right: 8px;
    }
}

h1 {
    font-weight: normal;
    margin-top: 0;
}

input[type="text"] {
    display:block;
    width: 100%;
    font-family: roboto;
}

.credits {
    margin-top: 30px;
    border-top: 1px solid #ddd;
    text-align: center;
}

(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);
        }
    }

})();
<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>

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

<modal id="custom-modal-2">
    <div class="modal">
        <div class="modal-body">
            <h1 style="height:1000px">A Tall Custom Modal!</h1>
            <button ng-click="vm.closeModal('custom-modal-2');">Close</button>
        </div>
    </div>
    <div class="modal-background"></div>
</modal>
(function () {
    'use strict';

    angular
        .module('app', ['ui.router'])
        .config(config)
        .run(run);

    function config($stateProvider, $urlRouterProvider) {
        // default route
        $urlRouterProvider.otherwise("/");

        // app routes
        $stateProvider
            .state('home', {
                url: '/',
                templateUrl: 'home/index.view.html',
                controller: 'Home.IndexController',
                controllerAs: 'vm'
            })
            .state('test-page', {
                url: '/test-page',
                templateUrl: 'test-page/index.view.html',
                controller: 'TestPage.IndexController',
                controllerAs: 'vm'
            });
    }

    function run() {
    }
})();
(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');
                }
            }
        };
    }
})();
(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();
        }
    }

})();
(function () {
    'use strict';

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

    function Controller() {
        var vm = this;

        initController();

        function initController() {
        }
    }

})();
<div class="col-md-6 col-md-offset-3">
    <h1>Test Page</h1>
    <p>This one doesn't have any modals...</p>
</div>
/* 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;
}