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].
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 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.
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”.
One thing is interesting here. It is the buttons attribute:
This is the name of the directive which should be tied to this element. Now let’s create our modal.
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:
This button is not tied to any controller. But still I expect my “openDialog” directive to do some magic.
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.
Then I create the ModalCtrl. This is the Controller which is used by my Bootstrap Modal block.
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.
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
. 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
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
. 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.
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.