Show a Twitter Bootstrap Modal with Angular.js Directives

von

My use case: I have a modal which has a controller A. I want to call it from outside this controller. As I found out this is not so trivial because you need to understand how Angular.js Directives [1] work. The Angular.js. docs explain: Controllers should not be used for DOM manipulation (like showing a modal or not) but only for business logic. The reason is that visual changes cannot be tested easily and Controllers are subject to unit tests. Instead the aspiring Angular.js developer need to understand Angular.js Directives.

Details on my use case: I have a list of entries. If I click one, the modal should appear with the relevant data to edit. I also want the user to be able to add a new set of data. Thus I need a button which is not part of any controller and opens my modal. The modal is created with Twitter Bootstrap [2].

What is a Angular.js Directive?

The docs say: “Directives are a way to teach HTML new tricks. During DOM compilation directives are matched against the HTML and executed. This allows directives to register behavior, or transform the DOM.”. Basically it means you can create your own tags or attributes which cause Angular.js to do something. Directives can replace HTML code with their own code. They can work with a controllers model and call other controllers.

Unfortunately as much power as they have, they are not trivial to understand. In this post we’ll see an example which does not look complex, but actually caused me some headache. So far there are many competent Angular.js developers out there, but the docs do not show too many examples which you could use in real life. Anyway lets go.

The HTML Code

The following is a HTML page which will frame my relevant stuff later. For the first step I just included the Bootstrap required files, the Angular.js file and my own Application file. On the top of the code you can see I use the HTML5 doctype which is recommended by Bootstrap. In line two I initialize my Angular.js app with the module “directives”. This module contains the directive code, as the name suggests.

<!DOCTYPE html>
<html ng-app="directives">
  <head>
    <title>Open a Bootstrap Modal with Angular</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>

  <!-- Controllers and Modal and everything else will go here -->

   <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
   <script src="js/bootstrap.min.js"></script>
   <script src="http://code.angularjs.org/angular-1.0.1.min.js"></script>
   <script src="js/application.js"></script>    
  </body>
</html>

Now I’ll add a div which contains nothing else than an Input-Field, tied to the Model named “blub”. I show the value of this model besides the Input Field. A second tag is included: a simple button, formatted by Bootstrap. The whole block gets a controller, namely “SomeCtrl”.

<div ng-controller="SomeCtrl">
  <input type="text" ng-model="blub" /> Value 
  <button open-dialog class="btn btn-primary">Test</button>
</div>

One thing is interesting here. It is the buttons attribute:

openDialog

This is the name of the directive which should be tied to this element. Now let’s create our modal.

<div class="modal hide fade" id="myModal" ng-controller="ModalCtrl">
   <div class="modal-body">
      <p>Body: </p>
   </div>
</div>

For the sake of readability I have removed modal-header and modal-footer. You can interpret this now as a Twitter Bootstrap Modal which is hidden by default. The div got a controller: ModalCtrl. Inside the modal I want to show a model called “data”. It needs to be in the scope of the ModalCtrl.

Outside of all controllers I put another button:

<a href="#myModal" open-dialog role="button" class="btn">Add something</a>

This button is not tied to any controller. But still I expect my “openDialog” directive to do some magic.

Angular.js in Action

First, I create a Controller for SomeCtrl. It’s pretty simple as it does only hold a single model called “blub” which contains a string.

function SomeCtrl($scope) {
   $scope.blub = "Test";
}

Then I create the ModalCtrl. This is the Controller which is used by my Bootstrap Modal block.

function ModalCtrl($scope) {
   this.setModel = function(data) {
      $scope.$apply( function() {
         $scope.data = data;
      });
   }
   $scope.setModel = this.setModel;     
}

The ModalCtrl gets a function called setModel. I add it to the object itself as well as to the scope. When used in scope, I can use it on the HTML side of life. But for the directive I need it as plain JS-method too.

Inside of the setModel function I use the $apply method [3] which is pretty interesting. Because I will change my model from outside of Angular.js, I need to apply the changes to it. It is like telling Angular.js that the specific expression should now be part of the framework again. Please see the documentation I referenced for further details.

Now we can write the directive.

angular.module('directives', []).directive('opendialog',
   function() {
      var openDialog = {
         link :   function(scope, element, attrs) {
            function openDialog() {
              var element = angular.element('#myModal');
              var ctrl = element.controller();
              ctrl.setModel(scope.blub);
              element.modal('show');
            }
            element.bind('click', openDialog);
       }
   }
   return openDialog;
});   

The first line creates a module called “directives”. it gets a directive with the name “opendialog”. The unnamed function we pass as argument returns the creation object of this directive. This object does only get a new function in the link property. The link function is described like that:

Link function allows the directive to register listeners to the specific cloned DOM element instance as well as to copy content into the DOM from the scope.

  • Angular.js Docs

Link functions are very common for directives as the docs say further. In our case we want to add a click event to whatever we name “opendialog”. This is done with

element.bind('click', openDialog);

. You see, it is plain jQuery here. The element in this case is the element which has been marked as “opendialog” element.

The included openDialog() function is interesting, but trivial. First we can select the modal with

angular.element('#myModal');

It’s using jQuery under the hood, but if we do it the Angular-way, we can get access to the controller which is used by this element. With the controller we can call the previously created setModel-function and put the model from our current scope to there. The current scope - this is the SomeCtrl or none at all, depending on which button we click.

Finally, when the data is set I call

element.modal('show')

. The modal-function is added by Bootstrap. You can find some more docs at link [4].

Now you should be able to see your model passed to your modal-controller. And of course your Modal should appear when you click on a button.

Conclusion

This was a more trivial example of using Angular.js together with Bootstrap. It might become a little more difficult if you use it with Bootstrap tabs. My hope is that everything in my code becomes easier with Angular.js. At the moment of this writing I’m a little afraid my code will be severed in to many little pieces. We’ll see.

Another thing is, I am not 100% sure if this blog post reflects the best solution to my use case. It seems to be fine when I follow the docs. But the lack of real-life examples doesn’t make it easy to confirm if this is good code or not. At least there might be one another solution for my problem: you could fire an event in a Controller A which is then caught by Controller B. A “Data Service Bus” can carry the data for you. But I found this a little bit overkill and so far I am happy with the code above.

Reference:

Tags: #AngularJS #JavaScript #Open Source

Newsletter

ABMELDEN