In AngularJS, you have your Views, which present data to the user; you have your Controllers, which manage the $scope (ie. view model) and expose behavior to the View; and, you have your Directives, which link user interactions to $scope behaviors. But then you also have a special kind of Controller - a Directive Controller. The Directive Controller is defined within the context of one directive; but, it can be injected into other directives as a means to facilitate inter-directive communication.
When you start using AngularJS, you definitely need to change the way you think about application architecture. AngularJS enforces a very strict separation of responsibilities, making sure that your DOM updates are abstracted away from your model updates. Directives act as the layer that keeps these two aspects loosely coupled. One one hand, directives translate data into user interfaces; and, on the other hand, directives translate user interactions back into $scope behaviors.
So, where do Directive Controllers fit into this model? Well, I'm still not 100% sure. So far, I've only just begun to really play around with directive controllers and how inter-directive communication works. As such, I'm not sure I can even codify underlying rules. Here's what I think as of this writing:
- Link functions capture user behavior.
- Link functions execute $scope.$apply() calls.
- Directive controllers can assume an active $digest, given the rule above.
- Directive controllers can alter the DOM, but should defer to the Link functions for user interactions.
Those are just some raw thoughts, so it may not make too much sense. That said, between Views, Controllers, Directives, and Directive Controllers, how do you figure out what functionality goes where?
That said, there is one underlying concept that I want to drive home: as you add user interaction behavior, minimize the number of $digests that run. While AngularJS allows you to render a view based on $scope data, you should try to use direct DOM manipulation as much as possible while in the context of a directive. Defer updating the $scope and invoking $scope.$apply() as long as possible. This will keep your page feeing much more responsive.
If you look at the code above, you can see that the position of each slave handle is defined by an ngStyle directive; this maps the $scope data onto CSS properties. However, if you look at the video (or use try the code), you'll notice that the X/Y coordinates in the "leaderboard" don't update until you actually release the mouse. This is because I am deferring $scope updates (and subsequent $digests) until the movement has concluded.
The more I dig into the AngularJS, the more I really love it! The separation of concerns is awesome; and, I think it leads to a much more maintainable application. Figuring out what goes where, however, can be a little confusing at times. Hopefully this exploration may help.
Want to use code from this post? Check out the license.