Top 10 mistakes made when developing on AngularJS
- Transfer
AngularJS is currently one of the most popular javascript frameworks. Its use simplifies the development process, making AngularJS an excellent tool for creating small web applications, but the framework’s capabilities are not limited to this and allow you to develop large applications full of diverse functionality. The combination of ease of development and a large number of features led to widespread distribution, and along with the distribution there were typical, often encountered errors. This topic describes the most common errors encountered when developing large projects on AngularJS.
1. Folder structure corresponding to MVC applications
AngularJS is an MVC framework. Despite the fact that the models in it are not defined as explicitly as in the case of backbone.js, the overall architectural style remains the same. A common practice when using MVC frameworks is to group files according to the following pattern:templates/
_login.html
_feed.html
app/
app.js
controllers/
LoginController.js
FeedController.js
directives/
FeedEntryDirective.js
services/
LoginService.js
FeedService.js
filters/
CapatalizeFilter.js
This approach is common, especially among developers with experience working on RoR. Nevertheless, with the growth of the application, the use of such a folder structure leads to the fact that at each point in time you have to keep several folders open. Whatever you use - Sublime, Visual Studio or Vim with NerdTree - when you move through the directory tree, you will constantly spend time scrolling. To avoid this, you can group files by functionality, and not by type:app/
app.js
Feed/
_feed.html
FeedController.js
FeedEntryDirective.js
FeedService.js
Login/
_login.html
LoginController.js
LoginService.js
Shared/
CapatalizeFilter.js
This folder structure makes it much easier to search for related files related to the same feature, which can speed up the development process. Yes, it may seem controversial to store html files in one folder along with js, but the effect of saving time can be more important.2. Modules (or lack thereof)
Often, at the beginning of project development, all the functionality is added to a single module. Until some point, this approach works, but as the project develops, the code becomes unmanageable.var app = angular.module('app',[]);
app.service('MyService', function(){
//service code
});
app.controller('MyCtrl', function($scope, MyService){
//controller code
});
The next widespread approach is to group objects by their type:var services = angular.module('services',[]);
services.service('MyService', function(){
//service code
});
var controllers = angular.module('controllers',['services']);
controllers.controller('MyCtrl', function($scope, MyService){
//controller code
});
var app = angular.module('app',['controllers', 'services']);
This approach also scales not in the best way, like the directory structure from point 1. To achieve better scalability, we will follow the same concept of breaking code into features:var sharedServicesModule = angular.module('sharedServices',[]);
sharedServices.service('NetworkService', function($http){});
var loginModule = angular.module('login',['sharedServices']);
loginModule.service('loginService', function(NetworkService){});
loginModule.controller('loginCtrl', function($scope, loginService){});
var app = angular.module('app', ['sharedServices', 'login']);
Dividing the functionality into different modules also makes it possible to reuse code in various projects. 3. Dependency injection
Dependency injection is one of the best features provided by AngularJS. DI makes testing easier and cleaner. AngularJS is very flexible on how dependencies can be implemented. The easiest way is to pass the dependency to the function as a parameter:var app = angular.module('app',[]);
app.controller('MainCtrl', function($scope, $timeout){
$timeout(function(){
console.log($scope);
}, 1000);
});
From the code it is clear that MainCtrl depends on $ scope and $ timeout. This works great until the project goes into production and you want to minify your code. Using UglifyJS to the above code will result in the following:var app=angular.module("app",[]);app.controller("MainCtrl",function(e,t){t(function(){console.log(e)},1e3)})
Now AngularJS does not know what MainCtrl really depends on. To prevent this from happening, there is a very simple solution - to pass the dependencies as an array of strings, with the last element in the form of a function that accepts all of the listed dependencies as parameters:app.controller('MainCtrl', ['$scope', '$timeout', function($scope, $timeout){
$timeout(function(){
console.log($scope);
}, 1000);
}]);
The code above will be converted by the minifier to code that AngularJS can already correctly interpret:app.controller("MainCtrl",["$scope","$timeout",function(e,t){t(function(){console.log(e)},1e3)}])
3.1. Global dependencies
Often, when developing an AngularJS application, it becomes necessary to use objects available anywhere in the application. This breaks down a well-built model based on dependency injection and leads to bugs and complicates the testing process. AngularJS allows you to wrap such objects in modules so that they can be embedded like regular AngularJS modules. For example, the excellent Underscore.js library can be wrapped in a module as follows:var underscore = angular.module('underscore', []);
underscore.factory('_', function() {
return window._; //Underscore must already be loaded on the page
});
var app = angular.module('app', ['underscore']);
app.controller('MainCtrl', ['$scope', '_', function($scope, _) {
init = function() {
_.keys($scope);
}
init();
}]);
This allows the application to use a single style with the mandatory implementation of dependencies and leaves it possible to test modules in isolation from the functionality of their dependencies. 4. Inflating controllers
Controllers are the foundation of AngularJS. And often, especially beginners, write too much logic in the controllers. Controllers should not manipulate the DOM or contain DOM selectors; there are directives for this. Likewise, business logic must be in the services. Data should also be stored in services (unless data is tied to $ scope), because services, unlike controllers, are singletones whose lifetime coincides with the lifetime of the application itself. When developing controllers, it is best to follow the principle of sole responsibility (SRP) and consider the controller as the coordinator between the view and the model, in this case there will be a minimum of logic in it.5. Service vs Factory
These naming conventions make every newbie in AngularJS embarrassing, although in reality they are almost the same. Let's see the source code for AngularJS:function factory(name, factoryFn) {
return provider(name, { $get: factoryFn });
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
The service function simply calls the factory function, in which the call to the provider function is wrapped. If service just calls the factory function, what is the difference between the two? The point is $ injector.instantiate, inside which $ injector creates a new instance of the service constructor function. An example of a service and a factory performing the same actions:var app = angular.module('app',[]);
app.service('helloWorldService', function(){
this.hello = function() {
return "Hello World";
};
});
app.factory('helloWorldFactory', function(){
return {
hello: function() {
return "Hello World";
}
}
});
When helloWorldService or helloWorldFactory are injected into the controller, they will both have a single method that returns “Hello World”. Since all providers are singleton, we will always have only one instance of the service and one instance of the factory. So why do factories and services exist simultaneously if they perform the same function? Factories provide more flexibility because they can return a function that can create new objects. In OOP, a factory is an object that creates other objects:app.factory('helloFactory', function() {
return function(name) {
this.name = name;
this.hello = function() {
return "Hello " + this.name;
};
};
});
Here is an example of a controller using a service and two factories:app.controller('helloCtrl', function($scope, helloWorldService, helloWorldFactory, helloFactory) {
init = function() {
helloWorldService.hello(); //'Hello World'
helloWorldFactory.hello(); //'Hello World'
new helloFactory('Readers').hello() //'Hello Readers'
}
init();
});
Factories can also be useful when developing classes with private methods:app.factory('privateFactory', function(){
var privateFunc = function(name) {
return name.split("").reverse().join(""); //reverses the name
};
return {
hello: function(name){
return "Hello " + privateFunc(name);
}
};
});
6. Failure to use Batarang
Batarang is a Chrome browser extension for developing and debugging AngularJS applications. Batarang allows you to:- view models attached to scopes
- build a dependency graph in the application
- analyze application performance
7. Too many observers
As noted above, AngularJS is pretty productive out of the box. But, when the number of observers reaches the number 2000, the $ digest cycle, in which the data changes are checked, may begin to slow down the application. Although reaching 2000 does not guarantee a slowdown, it is a good starting point from which to worry. Using the following code, you can find out the number of observers per page:(function () {
var root = $(document.getElementsByTagName('body'));
var watchers = [];
var f = function (element) {
if (element.data().hasOwnProperty('$scope')) {
angular.forEach(element.data().$scope.$$watchers, function (watcher) {
watchers.push(watcher);
});
}
angular.forEach(element.children(), function (childElement) {
f($(childElement));
});
};
f(root);
console.log(watchers.length);
})();
Using the code above and the Batarang observer tree, you can see if you have duplicate observers or observers over immutable data. In the case of immutable data, you can use the bindonce directive so as not to increase the number of observers on the page.8. Inheritance of scopes ($ scope's)
JS prototype-based inheritance is different from classical class inheritance. Usually this is not a problem, but these nuances can occur when working with scopes. In AngularJS, a regular (non-isolated) $ scope is inherited from the parent to the oldest ancestor $ rootScope. A common data model shared by a parent scope with a child is easily organized thanks to prototype inheritance. In the following example, we want the username to appear simultaneously in two span elements after the user enters his name.{{user}}{{user}}
Now the question is: when the user enters his name in the text box, in which elements will it be displayed: navCtrl, loginCtrl, or both? If your answer is loginCtrl, you understand how prototype-based inheritance works. No string of prototypes is used in string field searches. To achieve the desired behavior, it is advisable for us to use the object to correctly update the username in the child and parent $ scope. (I recall that in JS, functions and arrays are also objects.){{user.name}}{{user.name}}
Now, since the user variable is an object, the prototype chain will work and the span element in navCtrl will be correctly updated along with loginCtrl. This may seem like an unnatural example, but when working with directives that create child scopes (like ngRepeat), such moments will arise.