This talk I will walk through creating a modal window that is generic in the sense that it will take any old arbitrary html and stuff it in a modal window that is used throughout your site. This uses the Angular Bootstrap UI Modal window and wraps that directive in another directive. You can find the source code to a demo that I have created for this here. I have broken this down into the major sections in which, hopefully, this will be explained. You can either follow along or just look at the code located on the github link above. So let's get started.
GitHub Demo Repo
Modal Directive's Structure
Like described above my modal directive wraps the Angular-UI-Bootstrap modal.
The Need for a Generic Modal Window
Ok. So let's start with my use case for a modal window. I have a website, and this website needs to have about 15 modal windows. Because this website does not have any navigation all information, other than that on the main page, needs to use a modal window. For example, an author's bio or an excerpt from their book. So I have two options 1. I code one modal window for every instance I need, or 2. Define a directive that allows me to create a modal window and shove any old HTML partial in. Guess what I chose?
Derective Definition Object(DDO)
The directive took a couple of iterations to create, and still could use some improvements, but overall it fills the use case very well.
module.directive('myModal', function($modal){
return {
transclude: true,
restrict: 'EA',
template: '<a ng-click="open()" ng-transclude>{{name}}</a>',
scope: {
useCtrl: "@",
email: "@"
},
link: function(scope, element, attrs) {
scope.open = function(){
var modalInstance = $modal.open({
templateUrl: templateDir+attrs.instanceTemplate +'.tpl.html',
controller: scope.useCtrl,
size: 'lg',
windowClass: 'app-modal-window',
backdrop: true,
resolve: {
custEmail: function(){
return {email: scope.email};
}
}
});
modalInstance.result.then(function(){
console.log('Finished');
}, function(){
console.log('Modal dismissed at : ' + new Date());
});
};
}
};
});
So listed above is the main directive also known as the Derective Definition Object(DDO). This basically creates an HTML element named myModal
. There are 3 important sections to note here. First, we have the section:
return {
transclude: true,
restrict: 'EA',
template: '<a ng-click="open()" ng-transclude>{{name}}</a>',
scope: {
useCtrl: "@",
email: "@"
},
This code allows the outer HTML file to use the myModal
element or attribute. This is allowed by listing the restrict
to EA
. Also, it allows the myModal
element to list attributes to be passed into the directive.
Tranclusion
Now I added transclusion tag to the JSON object becuase I needed to be able to use the HTML that was inside of the myModal
element. One use case was that I needed to make an image a link for the modal to open. For example, take this HTML snippet:
<myModal>
<img src="img/content/car.png"/>
</myModal>
Now when this is compiled by Angular's HTML compiler it will insert the inner HTML of myModal
tag into the template
tag that is defined above, which is just a wrapper for an anchor tag to that calls the open function of the modal instance.
Isolate Scope
Isolate scope is definitely something I needed, since I will be opening up a ton of these and if they all had the same scope the last one to be written to the outer HTML page would overwrite all the previous listed myModal
tags. So above I used an isolate scope that recieves 2 attributes from the myModal
element. First, is probably the most important and that defines which controller you want your modal to use. Second, is an email address. Now the email address attribute can be totally removed from this directive, but I thought I would show an example of passing information from the encasing HTML to the directive then to the modal instance's controller, which I will go over in depth in the [Passing Data from Main Page to the Instance Controller] [passingData]. Now time to talk about how I wrapped Angular UI Boostrap's modal instance inside my own directive.
Ecapsulating the Bootstrap UI's Modal Instance Service
Instead of creating my own modal window I saw that Angular UI Bootstrap has its own single modal directive. So instead of reinventing the wheel for the modal I went ahead and used theirs.
scope.open = function(){
var modalInstance = $modal.open({
templateUrl: templateDir+attrs.instanceTemplate +'.tpl.html',
controller: scope.useCtrl,
size: 'lg',
windowClass: 'app-modal-window',
backdrop: true,
resolve: {
custEmail: function(){
return {email: scope.email};
}
}
});
modalInstance.result.then(function(){
console.log('Finished');
}, function(){
console.log('Modal dismissed at : ' + new Date());
});
};
If you are not familiar with Angular UI Bootstrap I highly suggest reviewing it, so you can know how the UI Boostrap modal instance works and all of its other options which you can pass into the instance.
Since this is placed into the linking method in the directive it specifies what actions you want your new HTML 'myModal' elements to do. For this case, we want it to pop up a modal window, and thats just what this does. Basically, the outer HTML has an ng-click='open()'
and this then just calls the open
function on the UI Bootstrap's modal instance directive. Now this directive takes a couple of options/parameters. Now, like I said above, there are around 15 options you can use for UI Boostrap's modal, so if you need to customize you modal window to do other things check them out first. I will only go over the ones that are contextable to my directive, which is the controller
,template
, and resolve
.
Specifying the Template/HTML to use
The template that you would like to use is passed in as an attribute included in the myModal
element. For example,
<my-modal name='RedBull' instance_template="redbull" use_ctrl="RedBullCtrl">
RedBull
</my-modal>
This instance-template
attribute is actually the name of the template you want to display inside of the modal window. Now my directive looks for templates in the /views/templates/
directory, which is defined in the angular module which holds the directive, but you can specify it as anywhere you keep your templates. In addition, I put the .tpl.html
extension inside the directive, so I can make my markup more declaritive then have to put it on within the HTML.
Specifying the Controller
The controller for your modal instance is done with an attribute. This is just like declaring the template except this time the attribute is use_ctrl
. For example, if I wanted to use RedBullCtrl
I would just declare it as use_ctrl='RedBullCtrl
. This is all shown in the HTML snippet above. Now remember I am wrapping the modal window instance, which creates its own scope for each modal instance controller. This is both good and bad. First, the good. Since I declared my modal window with isolate scope each scope is separate from another, which allows us to reuse the same controller if we wanted to. Now the bad. I have about 20 modal windows. Each one has its own scope and sometimes a child scope. This means I might have 30 or more scopes to deal with. Ok, so it wasn't too bad.
Passing Data from Main Page to the Instance Controller
Now say we have information which we need to pass from the HTML that houses our modal window to our directive then to our modal instance where we can use it in our modal instance's scope.
Attributes...Attributes...Attributes
Once again we jump to the help of attributes within our modal. Now just a forewarning, when you start to add attributes to your modal to pass relevant information about a type of modal your making you start making your directive specific instead of generalized. So maybe there is a better way to do this, but this is what I did to pass information. First, you declare an attribute in the DDO isolate's scope like so:
scope: {
useCtrl: "@",
email: "@"
},
Here I declare three attributes displayed with an @ sign this tells the isolate scope to pick up the attribute from the HTML and stuff it in here. So useCtrl
is obviously the attribute I just talked about. The next two are what we are concerned with. In combination with these attributes and the resolve
option used by UI-Bootstrap's modal instance we can pass our attribute values straight into the modal instance's controller like so:
var modalInstance = $modal.open({
templateUrl: templateDir+attrs.instanceTemplate +'.tpl.html',
controller: scope.useCtrl,
size: 'lg',
windowClass: 'app-modal-window',
backdrop: true,
resolve: {
custEmail: function(){
return {email: scope.email};
}
}
});
Add it to the Modal Instance Controller
Now you can just add this to the modal instance's controller like this:
module.controller('EmailAuthorCtl', function($scope, $modalInstance, $http, custEmail){
...
}
And thats it!! Now you have a generic modal window which you can reuse all over your application, so you dont have to recreate one everytime. Of course I have my demo listed in my generic-modal github repo, which includes all the source code you need to make your own generic modal window.
Comment Please
Please leave comments/recommendations or even code improvement suggestions, as I am sure this can be refactored
Up Next
Compiling HTML on the Fly via $compileProvider Directive
Winston Logger in NodeJS - Chiiillll Winston, I'm Just Blogging on Logging
NPM/Bower Link - Make Friends with Your Current Dev Node Modules and Bower Components Today