Using the DOM in an AngularJS application
Thursday 05 September 2013
- Getting started with AngularJS
- Creating an AngularJS Controller
- The AngularJS $scope is not the MVC Model
- Using repeating elements in AngularJS
- Filtering the data in an AngularJS ngRepeat element
- Using the AngularJS FormController to control form submission
- Creating an AngularJS Directive
- Using the DOM in an AngularJS application
- To SPA or not to SPA
Even though you should not manipulate the DOM in an AngularJS application but leave that up to AngularJS itself to do there are certainly places where you will need to interact with it. For example you might want to react to events and do something in reusable manner. Sure you can use the ngClick directive to react to a click but there are cases where you need to react to a click and don't want to repeat the same click handler each time.
The place to add these kind of interactive DOM related code in AngularJS is in a directive and not in a controller. In fact a controller should never be aware of the DOM at all to keep it testable.
In the previous blog post I showed how to use a directive with a template to make our HTML smaller and more reusable, in this blog post I am going to show how you could implement your own version of ngClick. Not that you need one but it's an easy example of how to get started with DOM manipulation from a directive :-)
A basic interactive directive
Creating a basic AngularJS directive is easy, just call the directive() function with a name and a function to create our directive. This function will be called by angular when needed and it returns an object just like before. Except in this case we are going to provide a link function. This link function is called with the scope, DOM element and attributes and lets you manipulate the DOM element as needed. In this case to add a click event handler.
The scope variable is easy enough, that is the current AngularJS scope object. The element is the element the directive was added to, in this case we didn't specify a restriction so the directive is used as an attribute. So what you get a element depends a bit. By default you will get a jQLite object which is small version of jQuery. However if you also include a version of jQuery before loading angular this will be a full jQuery object. Either way we can bind an event handler and react to a click event.
Making the directive more configurable
Creating a directive that just displays a hard coded message is not very useful so it would be nice if we can read the function to execute from the HTML attribute just as with the standard ngClick directive. So we basically wan to write some markup like this:
Turn out that the attr parameter passed to the link function will contain the string we are looking for. All we need to do is call it. We could just eval() this string but that would most likely not work so we need another more reliable approach. It turns out AngularJS provided the $parse service just for this goal. So an updated version of the directive would look like this:
And with this change our doIt() function on the scope would be executed just fine.
One last missing piece
The code above is close but not quite finished. If we decide to modify a property on the $scope in the function being called we never see the UI actually being updated. The reason is that AngularJS is not aware of the DOM event firing and therefore not aware of the fact that the $scope might be updated. We could let AngularJS know this in out function but in that case we would have to repeat the same code each time and with the ngClick we don't need to either.
Turns out the fix is quite simple. Just wrap the function call inside a $scope.$apply() call and all of a sudden AngularJS is aware of your changes and will update the UI as needed. With this final change we are all good to go :-)
With this we are almost duplicating the original ngClick directive, the only difference left is that the real ngClick handler adds the DOM event args to the function call so it can be used when needed. Other than that it is identical.
This shows that even working with the DOM isn't hard with AngularJS as long as you put things in the right place.