AngularJS Code Smell: Defining $scope Methods In Directives
I've touched on this idea before, in my AngularJS directive mindset post; but, I've seen this happen so many times that I wanted to call it out again, as its own concept. If you define $scope methods inside of your AngularJS directives, you might want to consider this a "code smell." Take a moment to think about what that method is doing and consider moving it into the parent Controller or Service.
NOTE: This does not necessarily apply to AngularJS directives that use the Isolate scope.
In AngularJS, directives are the "glue" that binds the View to the Controller. In one direction, they take the data provided by the Controller and help render some of the View. In the other direction, they take JavaScript events, initiated on the DOM (Document Object Model), and pipe them into the hooks exposed by the Controller. Directives are a conduit of communication, not an origin of behavior.
If you find that you are defining $scope methods in your Directives, you're not facilitating communication - you're exposing behavior to the View. Probably, what you want to do is move that $scope method into the parent Controller and then either invoke it directly in the View; or, create an event handler in the Directive that turns around and consumes said $scope method (calling $apply(), $evalAsync(), or $digest() as needed).
This code smell is based on my own experience with AngularJS and the type of directives that I have written. As my applications evolve, I can feel the pain points of poor decision making. And, to me, defining behavior in my Directives has caused pain when it comes to maintaining and refactoring my AngularJS code. Your mileage may vary.
Reader Comments
@Ben - Amen brother!
@Josh,
Ha ha, I got the idea for this post after we reviewed some of that crazy code!
We wish for your next post.Good tut
@Ben - I'm glad you took my ranting to heart. I just saw some code like this in a directive and thought of this post.
$scope.$parent.newFunction = function(){...};
eeeek.
@Josh,
That line of code is terrifying and will keep me up at night :D
I'm fairly new to angular, but have also felt a sense of unease when assigning functionality to a shared scope through a directive.
However, by treating a controllers scope as a view model, directives (appear to - due to my limited experience) become a useful/scalable way to compose reusable behaviour onto a scope.
At the moment i'm struggling to see a better way to do this without directives. What would you suggest?
@Ben, I have a scenario where I am using common directive for two controllers.
For the common functionality I have added methods directly to the inherited scope, so that I don't have to duplicate the code in different controller.
What do you suggest in this scenario? What would be the best approach in this case?
@Basha,
Long time since your question but I'm also interested in this. It kinda seems that Angular struggles to accommodate this scenario, which to me seems like a common scenario.
On a similar issue, where should we add DOM-manipulation directive behavior? For instance, if the behavior is to update the DOM via an ng-click action in a directive's view. The only way to trigger behavior in the view, that i've seen, is via $scope. Thus, the directive must add behavior to the scope. The other option is to add the method to $scope within the directive's controller, but then you'd have DOM manipulation in the controller.
Any ideas?