📜 ⬆️ ⬇️

Top10 mistakes made when developing on AngularJS

Currently AngularJS is one of the most popular javascript frameworks. Its use simplifies the development process, making AngularJS a great tool for creating small web applications, but the framework's capabilities are not limited to this and allow you to develop large applications filled with various functions. The combination of ease of development and a large number of possibilities led to a wide spreading, and along with the spread 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 a MVC framework. Despite the fact that the models in it are not as clearly defined 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 who have experience in RoR development. However, as the application grows, using a similar folder structure results in having to keep several folders open at any given time. Whatever you use - Sublime, Visual Studio or Vim with NerdTree - you will constantly spend time scrolling through the directory tree. To avoid this, you can group files by functionality, rather than 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 belonging to the same feature, which can speed up the development process. Yes, it may seem controversial - to store in the same folder html files with js, but the effect of saving time may be more important.

2. Modules (or lack thereof)

Often, at the beginning of the project development all the functionality is added to a single module. Until a certain moment, this approach works, but as the project progresses, 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 most common 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 does not scale well either, like the directory structure of clause 1. In order to achieve better scalability, we follow the same concept of breaking the 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']); 
Spreading functionality across modules also allows code reuse across projects.

3. Introduction of dependencies

Dependency injection (dependency injection) is one of the best features provided by AngularJS. DI facilitates the testing process and makes the code cleaner. AngularJS is very flexible in how dependencies can be implemented. The easiest way is to pass the dependency to a 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 minimize 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 doesn’t know what MainCtrl really depends on. To prevent this from happening, there is a very simple solution - transfer dependencies as an array of strings, with the last element as a function that accepts all the dependencies listed as parameters:
 app.controller('MainCtrl', ['$scope', '$timeout', function($scope, $timeout){ $timeout(function(){ console.log($scope); }, 1000); }]); 
The code above will be converted by minifiers into 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 that are available anywhere in the application. This breaks a coherent model based on dependency injection and leads to bugs and complicates the testing process. AngularJS allows you to wrap similar objects in modules so that they can be embedded like ordinary AngularJS modules. For example, the magnificent 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 mandatory dependency injection and leaves the opportunity to test modules in isolation from the functionality of their dependencies.

4. Inflating controllers

Controllers are the backbone of AngularJS. And often, especially beginners, they write too much logic in the controllers. Controllers should not manipulate the DOM or contain DOM selectors, for this there are directives. Similarly, business logic should be in services. Data should also be stored in services (except when data is tied to $ scope), because services, unlike controllers, are singletons, whose lifetime coincides with the lifetime of the application itself. When developing controllers, it is best to follow the sole responsibility principle (SRP) and consider the controller as the coordinator between the representation and the model, in which case the logic in it will be minimum.

5. Service vs Factory

These naming confuses every newcomer to AngularJS, although in reality they are almost the same. Let's look at the source code of 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, which wraps the call to the provider function. If service simply calls the factory function, what is the difference between them? The meaning is in $ injector.instantiate, within 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"; } } }); 
The moment helloWorldService or helloWorldFactory is injected into the controller, both of them will have a single method that returns “Hello World”. Since all providers are singletons, we will always have only one instance of the service and one instance of the factory. So why are there both factories and services at the same time, if they perform the same function? Factories offer 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. Non-use of Batarang

Batarang is a Chrome browser extension for developing and debugging AngularJS applications. Batarang allows you to:Despite the fact that the performance of AngularJS is not bad out of the box, with the growth of the application, with the addition of custom directives and complex logic, the application can begin to slow down. Using Batarang it is easy to figure out which of the functions is spending a lot of time when calling. Batarang also displays a watch tree, which can be useful when using a large number of watchers.

7. Too many observers

As noted above, AngularJS is quite 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 operation of the application. Although reaching a number of 2000 does not guarantee a slowdown, this is a good starting point from which you can begin 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 observer tree of the battery, you can see if you have duplicate observers or observers on immutable data. In the case of immutable data, you can use the bindonce directive so as not to increase the number of observers per page.

8. Scopes inheritance ($ scope's)

Prototype-based JS inheritance is different from classical class inheritance. This is usually not a problem, but these nuances can manifest themselves when working with scopes. In AngularJS, the normal (not isolated) $ scope is inherited from the parent to the most senior ancestor of $ rootScope. The shared data model, shared by the parent with the child, is organized easily due to inheritance on prototypes. In the following example, we want the user name to be displayed simultaneously in two span elements after the user enters his name.
 <div ng-controller="navCtrl"> <span>{{user}}</span> <div ng-controller="loginCtrl"> <span>{{user}}</span> <input ng-model="user"></input> </div> </div> 
Now the question is: when the user enters his name in the text field, in which elements it will be displayed: navCtrl, loginCtrl, or both? If your answer is loginCtrl, you understand how prototype-based inheritance works. In the search for string fields, a chain of prototypes is not used. To achieve the desired behavior, we want to use the object to correctly update the user name in the child and parent $ scope. (Remember, in JS, functions and arrays are also objects.)
 <div ng-controller="navCtrl"> <span>{{user.name}}</span> <div ng-controller="loginCtrl"> <span>{{user.name}}</span> <input ng-model="user.name"></input> </div> </div> 
Now, since the user variable is an object, the prototype chain will work and the span element in navCtrl will be updated correctly along with loginCtrl. This may look like an unnatural example, but when working with directives that create child scopes (like ngRepeat), such moments will arise.

9. Using manual testing

Until you start using TDD in your work, you will have to run the project every time and do manual testing to make sure your code works. There is no excuse for using this approach in the case of AngularJS. AngularJS was initially corrected so that the code developed on it was testable. DI, ngMock - your best helpers in this. There are also several tools that can take you to the next level.

9.1 Protractor

Unit tests are the basis for building a fully test-covered application, but as the project grows, using integration tests can be more effective for testing how viable the code is in the application. Fortunately, the AngularJS team has developed a great tool - Protractor, which is able to simulate user interaction. Protractor uses the Jasmine framework to write tests and has a good API for describing various interaction scenarios. Among the many different testing tools, Protractor has an advantage in understanding the internal structure of AngularJS, which is especially useful when you are dealing with something like $ digest cycles.

9.2. Karma

The AngularJS project team didn’t limit to writing test development tools. A Karma test runner was also developed. Karma allows you to perform tests every time you change files with source code. Karma is able to perform tests in parallel in several browsers. Various devices can also be targeted to a karma server to more fully cover real-world use cases.

10. Using jQuery

jQuery is a great library. It standardized cross-platform development and became a standard in modern web development. Despite the fact that jQuery has a large number of features, its philosophy is far from AngularJS. AngularJS is a framework for building applications, while jQuery is just a library that simplifies the interaction between JavaScript and HTML and provides a convenient API for working with AJAX. This is the fundamental difference between them. Angulyar is an approach to building applications, and not a way to control the layout of a document. To really understand the principles of building AngularJS applications, you should stop using jQuery. jQuery forces you to conform to an existing HTML standard, while Angular allows you to extend the HTML standard to the needs of your application. Manipulations with DOM in AngularJS should be made in directives, but it is quite possible to place wrappers in existing directives over existing jQuery components, if you could not find an analogue on angular.

Conclusion

AngularJS is a great, constantly evolving framework with a great community. I hope my list of popular errors will come in handy in your work.

')

Source: https://habr.com/ru/post/253103/


All Articles