In the "Angular Way," there is a strict separation of concerns. The Controllers aren't supposed to know anything about the DOM (Document Object Model); the Controllers simply manage the view-model and leave it up to the Directives to "glue" the view-model to the DOM. In the "Angular Way," the Directives are the only thing that should know about the DOM. And yet, you can inject the $element into your Controller constructor and you can pass the $event into your Controller using scope methods. Which begs the question: should injecting the $element and $event objects be considered an anti-pattern in AngularJS?
Personally, I lean towards Yes on this one - that it is an anti-pattern.
However, I will caveat that with saying that sometimes the simplicity of doing so (injecting $element or $event) may substantially outweigh the complexity of keeping things separate. I don't think there's anything that can't be accomplished with $watch() bindings and event-bindings inside a link() function. But, especially for one-off events, mutating the $element directly can be more straightforward than worrying about $watch() bindings.
A good example of that might be the native Form Controller that ships with AngularJS. In the Form Controller, the $element injectable is used to add various pristine, dirty, and other state-indicating classes onto the Form element in response to method invocation (ex, form.$setDirty()). Could each of these classes be added or removed by a $watch() binding in a link() function that was observing the changes in the Form Controller's view-model? Probably. But, it may be hard to argue that such a strict separation would make the code more peformant, easier to reason about, and easier to maintain.
So, personally, I think you should avoid injecting the $element object into the Controller constructor or passing the $event object into a Controller method. I think doing so blurs the lines and breaks-down the strict separation of concerns outlined in the "Angular Way." But, as Morpheus said about rules, "some of them can be bent; others can be broken." Just make sure you're operating based on educated decisions.
Looking For A New Job?
- ColdFusion Developer at Cavulus
- Senior Web Developer for SignUpGenius at SignUpGenius
- Florida Coldfusion Developer Opportunity at inetUSA
- Web Applications Developer at SiteVision, Inc.
I have a small follow-up (mostly academic) on being able to consume $event properties from within an AngularJS View without having to pass the $event object into the Controller:
While not necessarily practical, I believe it keeps the separation of concerns.
but what if I'm using the new angular 1.5 approach of only creating components instead of directives. I use controllers in components and so I am injecting $element.
I think in this case its impossible to distinguish between controller and directive.
I have not yet taken at the new .component() method in AngularJS 1.5, so I can't really speak to that. I'll try to look at it this holiday weekend so that I can get you a better response :D
I was reading another site this morning that talked about this issue, with much the same view point as your own. As an example of using such an anti-pattern, it pointed to the Angular-UI Bootstrap library. If you look at the directives, they're very trim, and their linking function does one thing: They call the controller.init() function, passing in the element and attributes. Their reasoning for this is testability. It is very hard to write unit tests for complex directives, but dead simple to write them for controllers. After going over what's being done in the source of Angular-UI, I really like how they've parsed it out.
Here's a link to that article:
I don't know much of anything about testing. And, especially nothing about testing the state of the DOM. But, I don't know - it just seems odd to me to move stuff into the Controller simply to make it easier to test. I thought keeping them separate was done to keep it easy to test. If the controller stands on its own, then you don't even need a DOM to test it. I mean, I thought was the whole point :D No need to mock out an entire DOM tree or feature-set. You just test the View-model and how it changes when you send commands to the Controller instance. It seems like if you throw the DOM in there, now the Controller becomes harder to test.
But again, I know very little about testing.
For me, keeping DOM and DOM event manipulation out of controller would be a must if I'm not sure if the target environment supports it. Directives can be replaced based on target environment quite easily as they don't contain any business.
Moreover keeping DOM out of the controller would ease the process of migrating my app from DOM-based platforms to others.