OPENING A MODAL WINDOW PROGRAMMATICALLY USING THE UI MODAL SERVICE

Opening a Modal Window Programmatically Using the UI Modal Service

Often in my programming gig, we maintain legacy applications and one of the common occurrences is that I see designers using the JQuery UI modal box in an angularJS application. So today’s article is for those who I will not name – I am going to show you how simple it is to open a modal box in angularJS. We shall touch upon how to define the modal box template, to open the modal window using the Angular UI Modal library, and to close the window gracefully using AngularJS scope events.

What you need to know!

  • Scopes and Data bindings.
  • What AngularJS Services are
  • and Dependency Injection

The modal Box "Component"

It helps to visualize the modal box as a reusable component that has a HTML template and a controller attached to it. The controller is responsible for maintaining and updating the state of the model bound to the modal box view. The HTML template for the modal box is shown below. Note the various input fields and the corresponding data model bindings that are used. The model properties are bound to the “vm” object, where vm signifies “view model” to indicate the tight binding between models defined(in the controller function) and the view.

The above modal box component template HTML is shown below on the left along with the angularJS controller code on the right.

Html Part

<div class="modal-header">
   <h3 id="modal-title" class="modal-title">
      {{vm.title}}
   </h3>
</div>
<div id="modal-desc" class="modal-body">
   <p>{{vm.body}}</p>
   <div>
      <form>
         <div class="form-group">
            <label for="exampleInputEmail1">Name :</label>
            <input type="text" class="form-control" ng-model="vm.user.name" placeholder="enter your name">
            <br>
         </div>
         <div class="form-group">
            <label for="exampleInputPassword1">
            Password :</label>
            <input type="password" class="form-control" ng-model="vm.user.password"
               placeholder="enter your password">
         </div>
      </form>
   </div>
</div>
<div class="modal-footer">
   <button type="button" class="btn btn-success" ng-click="vm.ok(vm.user)">Ok</button>
   <button type="button" class="btn btn-danger"
      ng-click="vm.cancel('modal dismissed')">Cancel</button>
   <button type="button" class="btn btn-danger" ng-click="vm.destroy()">Destroy MEEEE!!!</button>
</div>

Angular Code

function ModalBoxCtrl($scope, $uibModalInstance)
{
    var vm = this;
    vm.user = {
        name: '',
        password: ''
    }
    vm.title = 'Controller Modalbox';
    vm.body = 'Enter your Details';

    vm.ok = function (result) {
        //$uibModalInstance.close(result);//This also works
        $scope.$close(result);   
    }
    vm.cancel = function (message) {
        //$uibModalInstance.dismiss(message);//This also works
        $scope.$dismiss(message);
    }
    vm.destroy = function () {
        $scope.$destroy();
    }
}

The Modal box controller function is supplied with two objects $uibModalInstance and $scope which are resolved and dependency injected by the angular js injector subsystem.

The $uibModalInstance of the modal box is special in that it is available to be injected when a modal box is created. It provides two methods open and dismisses which perform their namesake functions.

The $scope object has two similar methods $close and $dismiss appended to it in addition to the modal’s content. It is possible to close or dismiss the modal without the need for a dedicated controller. So far, we have defined a few buttons in our modal box views (close, dismiss and Destroy MEEE!) that invoke the respective functions vm.ok, vm.cancel, and vm.destroy. Their purpose is described in the table below:

Functions in ‘ModalBoxCtrl’ Controller Description
vm.ok(vm.user) Execute $close function in $scope object and passess the vm.user object.It closes the modalbox and back to the main page.
vm.cancel(‘modal dismissed’) Execute $dismiss function in $scope object and passess the string value.It closes the modalbox and back to the main page.
vm.destroy() Execute $destroy function in $scope object.It destroy the scope of the modalbox.

Next, we discuss how to tie the modal controller to the modal box template. Angular UI makes that real easy by calling on its $uibModal service whose sole purpose is to open Modal windows. Therefore, the service has only one method defined – open(options). The options object takes on the properties explained in the table below:

Properties Description
appendTo The modal box template we defined is appended to specific element we desire. In our case, we attach it to an empty DIV tag. When the modal box is closed. it is removed from the DIV tag. The angular.element() is used to get a handle to our modalbox element through the id tag “#modalboxdiv”. We use the $document service in our controller to get hold of querySelector method.
template(*) The Modal box template (Nuff said)
bindToController if it is true, then the controller of the modalbox will have the property of the $scope object, If it is false, then the controller of the modalbox will not have the property of the $scope object. click here to know about this more. We can attach properties to the $scope object and to our controller alias object ‘vm’. bindToController allows us to attach $scope properties to ‘vm’ avoiding the issues of “scope soup”
controller Defines the name of the modalbox controller
controllerAs Defines the alias of the modalbox controller. ControllerAs functionality is similar to that offered by the ng-controller directives. The model variables are available through the object alias. In our case, we have used ‘vm’ as the alias. Then the user model in our view can be obtained through “vm.user”
scope(*) Define the scope object like ‘$rootscope’ or ‘$scope’ here to bind the view between the main page and modalbox template.

So to open a modal box with our template and controller, we can say

$uibOpen.open({
    appendTo: angular.element($document[0].querySelector('#modalboxdiv')),
    bindToController: true,
    controller: 'ModalBoxCtrl',
    controllerAs: vm,
    scope: $scope
});

The $uibModal.open (options) will open the modal box and returns a $uibModalInstance object which can be passed around. This is very useful if we decide to use the $uibModal in our own services rather than the controllers as we are doing now.

Opening the Modal from the Main view

The main view that opens the modal box is as follows:

When we click the ModalBox button, it invokes the vm.openModalbox() function defined in the MainCtrl Controller function which in turn calls $uibModal.open(options). This MainCtrl is attached to our main view which also houses the template injection point

<div id=”modalboxdiv”></div>

Html Part

<div class="container" ng-app="modalbox" ng-controller="MainCtrl as vm" ng-cloak>
   <div class="container">
      <div id="modalboxdiv"></div>
   </div>
   <div class="container">
      <div class="col-md-12">
         <div class="row">
            <div class="col-md-3">
               <p>Normal Modal box</p>
               <button type="button" class="btn btn-primary" ng-click="vm.openModalbox()">ModalBox</button>
            </div>
         </div>
      </div>
   </div>
</div

Angular Part

function MainCtrl($uibModal, $scope, $document) {
    var vm = this;
    var options = {
        appendTo: angular.element($document[0].querySelector('#modalboxdiv')),
        template: ,
        bindToController: true,
        controller: 'ModalBoxCtrl',
        controllerAs: vm,
        scope: $scope
    }

    vm.openModalbox = function () {
        $uibModal.open(options);
    }
}

Bringing in the ui.bootstrap module into angularJS

angular.module('modalbox', ['ui.bootstrap'])
    .controller('MainCtrl', MainCtrl)

MainCtrl.$inject = ['$uibModal'];

function MainCtrl($uibModal){
}

Thats it !!! A html runnable file that contains all the above code can be found here(Link)

A quick Note on bindToController in the options object of $uibModal(options)

‘vm’ is a javascript object which corresponds to an instance of the controller. Adding properties to the vm object instead of ‘this’ is good design practice. The ‘vm’ object can be used in the view template. In reality the vm object is added as a object property of the $scope object.

In general, $scope is used to bind the model between view and the controller. $scope itself has some inherited properties and methods from $rootScope. The methods manage important operations such as retrieval of DOM, event propagation and life cycle hooks.

When using the ng-controller directive, it injects the $scope object inside the DOM. The result is a messy object that consists of inherited properties and functions along with our own properties and methods that we attached in the controller. Not to mention when we start nesting controllers, we might end up using the same $scope model variables. Ex: $scope.heading in a panel and its a similar $scope.heading in the nested subpanel. This problem can be exacerabted in very complex applications. It would be helpful if we could namespace our methods and properties to an object besides $scope. This is why AngularJS introduced the controllerAs syntax so that the controller object is bound to the view instead of the $scope. We compare the two types of approaches below:

<div class="container" ng-app="modalbox" ng-controller="MainCtrl">
    {{name}}
</div>

Angular Part

function MainCtrl($scope) {
    $scope.name = “Scope code of MainCtrl”;
}

Using the controller object in the View:

Html Part

<div class="container" ng-app="modalbox" ng-controller="MainCtrl as vm">
    {{vm.name}}
</div>

Angular Part

function MainCtrl() {
    var vm = this;
    vm.name =  “ControllerAs code of MainCtrl”;
}

AngularJS ParentCtrl and testDirective

function TestCtrl() {
    this.age = '44';
}

function testDirective() {
    return {
        restrict: 'E',
        scope : {
            'scopeName' : '='
        },
        bindToController: {
            'bindToName': '='
        },
        controller: 'TestCtrl',
        controllerAs: 'vm',
        template: `
                <div>
                Name defined in scope : {{scopeName}} <br/>
                Name defined in bindToController : {{vm.bindToName}} <br />
                Age defined in controller : {{vm.age}}
                </div>
                `
    };
}

function ParentCtrl() {
    this.myName = 'Peter';
}

angular.module('app', [])
    .directive('testDirective', testDirective)
    .controller('TestCtrl', TestCtrl)
    .controller('ParentCtrl', ParentCtrl);

Html View

<body class="ng-cloak" ng-controller="ParentCtrl as vm">
    <test-directive scope-name="vm.myName" bind-to-name="vm.myName"></test-directive>
</body>

Notice that the directive has three options for model binding – scope, bindToController and the ControllerAs method that binds properties to vm inside our controller. The scope object is reminiscent of the first method of binding(similar to the “using the messy $scope object in the view” snippet we showed earlier), whereas the ControllerAs is the second way – the proper way(similar to the “using the controller object in the view” snippet shown earlier). The bindToController property was introduced so that properties are directly bound to the controller object instead of the scope. So it is good practice to use bindToController instead of the scope property in the directive definition object. I hope to write a more complete article on this topic later.

Leave a Comment