Below is the list of some mistakes that developers often make during the use of AngularJS functionalities, some learned lessons and solutions to them.
1. Manipulating DOM through the controller
It's legal, but must be avoided. Controllers are the places where you define your dependencies, bind your data to the view and make further business logic. You can technically manipulate the DOM in a controller, but whenever you need same or similar manipulation in another part of your app, another controller will be needed. So the best practice of this approach is creating a directive that includes all manipulations and use the directive throughout your app. Hence, the controller leaves the view intact and does it's job. In a directive, linking function is the best place to manipulate the DOM. It has full access to the scope and element, so using a directive, you can also take the advantage of reusability.
link: function($scope, element, attrs) {
//The best place to manipulate DOM
}
You can access DOM elements in linking function through several ways, such as the element
parameter, angular.element()
method, or pure Javascript.
2. Data binding in transclusion
AngularJS is famous with its two-way data binding. However you may encounter sometimes that your data is only one-way bound inside directives. Stop there, AngularJS is not wrong but probably you. Directives are a little dangerous places since child scopes and isolated scopes are involved. Assume you have the following directive with one transclusion
<my-dir>
<my-transclusion>
</my-transclusion>
</my-dir>
And inside my-transclusion, you have some elements which are bound to the data in the outer scope.
<my-dir>
<my-transclusion>
<input ng-model="name">
</my-transclusion>
</my-dir>
The above code will not work correctly. Here, transclusion creates a child scope and you can get the name variable, right, but whatever change you make to this variable will stay there. So, you can truly acces this variable as $parent.name. However, this use might not be the best practice. A better approach would be wrapping the variables inside an object. For example, in the controller you can create:
$scope.data = {
name: 'someName'
}
Then in the transclusion, you can access this variable via 'data' object and see that two-way binding works perfectly!
<input ng-model="data.name">
Not only in transclusions, but throughout the app, it's a good idea to use the dotted notation.
3. Multiple directives together
It is actually legal to use two directives together within the same element, as long as you obey by the rule: two isolated scopes cannot exist on the same element. Generally speaking, when creating a new custom directive, you allocate an isolated scope for easy parameter passing. Assuming that the directives myDirA and myDirB have isoleted scopes and myDirC has not, following element will be valid:
<input my-dir-a my-dirc>
whereas the following element will cause console error:
<input my-dir-a my-dir-b>
Therefore, directives must be used wisely, taking the scopes into consideration.
4. Misuse of $emit
$emit, $broadcast and $on, these work in a sender-receiver principle. In others words, they are a means of communication between controllers. For example, the following line emits the 'someEvent' from controller A, to be catched by the concerned controller B.
$scope.$emit('someEvent', args);
And the following line catches the 'someEvent'
$scope.$on('someEvent', function(){});
So far everything seems perfect. But remember that, if the controller B is not invoked yet, the event will not be caught, which means both emitter and receiver controllers have to be invoked to get this working. So again, if you are not sure that you definitely have to use $emit, building a service seems a better way.
5. Misuse of $scope.$watch
$scope.$watch is used for watching a variable change. Whenever a variable has changed, this method is invoked. However, one common mistake done is changing the variable inside $scope.$watch. This will cause inconsistency and infinite $digest loop at some point.
$scope.$watch('myCtrl.myVariable', function(newVal) {
this.myVariable++;
});
So in the above function, make sure you have no operations on myVariable and newVal.
6. Binding methods to views
This is one of the deadlisest sins. AngularJS has two-way binding, and whenever something changes, the views are updated many many times. So, if you bind a method to an attribute of a view, that method might potentially be called a hundred times, which also drives you crazy during debugging. However, there are only some attributes that are built for method binding, such as ng-click, ng-blur, ng-on-change, etc, that expect methods as paremeter. For instance, assume you have the following view in your markup:
<input ng-disabled="myCtrl.isDisabled()" ng-model="myCtrl.name">
Here you check the disabled status of the view via the method isDisabled. In the controller myCtrl, you have:
vm.isDisabled = function(){
if(someCondition)
return true;
else
return false;
}
In theory, it may seem correct but technically this will cause an overload, since the method will run countless times. In order to resolve this, you should bind a variable. In your controller, the following variable must exist:
vm.isDisabled
You can initiate this variable again in the activation of the controller
if(someCondition)
vm.isDisabled = true
else
vm.isDisabled = false
If the condition is not stable, you may bind this to another event. Then you should bind this variable to the view:
<input ng-disabled="myCtrl.isDisabled" ng-model="myCtrl.name">
Now, all the attributes of the view have what they expect and the methods will run only whenever needed.
7. Not using Angular's functionalities
AngularJS provides great convenience with some of its functionalities, not only simplifying your code but also making it more efficient. Some of these features are listed below: