Often in my programming gig, we maintain legacy applications and one of the common occurrences is that I see designers using the JQuery UI modalbox 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 modalbox 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 angularjs injector subsystem.

The $uibModalInstance of the modalbox is special in that it is available to be injected when a modal box is created. It provides two methods open and dismiss which performs their name sake 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:

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:

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

The main html template and the corresponding controller code are shown below:

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

The main controller function takes the $uibModal, $document and $scope objects which are dependency injected by the angularjs injector subsystem. The angular UI Modal service is avalable in the ui.bootstrap library and needs to be imported as module before we use it.

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

Using the messy $scope object in the view
<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”;
}

The second method is better practice in order to prevent pollution of the $scope object with our methods and properties. So this pattern pretty much works, until we encounter directives. Take a look at the following code snippet:

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.