Top 10 developer mistakes on AngularJS

Currently AngularJS is one of the most popular javascript frameworks. It simplifies the development process by making AngularJS a great tool for creating small web applications, but framework capabilities also enable the development of large applications, filled with a variety of functional. The combination of ease of development and a lot of opportunities have led to widespread and, together with the spread, typical common mistakes took place. This topic describes the most common mistakes that occur in the development of large projects on AngularJS.

1. Structure of folders, corresponding to MVC applications

AngularJS is an MVC framework. Despite the fact that the model it is not so clearly defined as in the case of backbone.js, the overall architectural style remains the same. Commonly used practice of using the MVC framework is grouping 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 for developers with experience in the development of RoR. However, with the growth of the use of such applications folder structure leads to the fact that you have to keep open multiple folders at once. Whatever you use – Sublime, Visual Studio or Vim and NerdTree – as you move through the directory tree you will constantly spend time while scrolling. 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 is much more simple to find related files, so it accelerates the development process. Yes, it may seem controversial – stored html files with js in the same folder, but the effect of saving time might be more important.

2. The modules (or lack of them)

Often, in the beginning, full functionality develops in a single module. Up to a certain point, this approach works, but then the project 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 isn’t scalable the best, too, like the directory structure in 1st case. In order to achieve better scalability, we will follow the same concept of grouping code by 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']);

 

Functional diversity of different modules also enables code reuse in different projects.

3. Dependency Injection

Dependency injection is one of the best opportunities offered by AngularJS. DI facilitates the testing process and makes the code clearer. AngularJS is very flexible on the count of relations that can be implemented. The easiest way is to pass dependency in a function as a parameter:

var app = angular.module('app',[]);
app.controller('MainCtrl', function($scope, $timeout){
    $timeout(function(){
        console.log($scope);
    }, 1000);
});

 

It’s clear that MainCtrl depends on $scope and $timeout. This works fine until the project goes into production, and you want to minify your code. Using UglifyJS will lead to the following code:

var app=angular.module("app",[]);app.controller("MainCtrl",function(e,t){t(function(){console.log(e)},1e3)})

 

Now AngularJS will not know what MainCtrl depends on. To avoid this, there is a very simple solution – to pass dependency as an array of strings with the last element that is a function that takes all of the dependencies as options:

app.controller('MainCtrl', ['$scope', '$timeout', function($scope, $timeout){
    $timeout(function(){
        console.log($scope);
    }, 1000);
}]);

 

The code above will be converted by minifier to code that AngularJS will be able to correctly interpret:

app.controller("MainCtrl",["$scope","$timeout",function(e,t){t(function(){console.log(e)},1e3)}])

 

3.1. Global dependence

Frequently, when developing AngularJS applications, there is a need to use objects that are available at any point of application. It breaks down a coherent model based on dependency injection, and leads to bugs and complexity of the testing process. AngularJS allows to wrap such objects in modules so they can be implemented like a conventional AngularJS modules. For example, the magnificent library Underscore.js can be wrapped in 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 introduction of mandatory dependencies and leaves the possibility to test the modules in isolation from their functional dependencies.

4. Inflating controllers

Controllers is the base of AngularJS. And often beginners write too much logic in the controllers. Controllers should not carry DOM manipulation or contain DOM selectors – directive exists for that. Likewise, the business logic must be in services. Data can also be stored in the services (except for the cases when the data is bound to the $scope), because the services, unlike controllers, are singletons, whose lifetime coincides with the lifetime of the application. When developing controllers, it’s the best to follow the single responsibility principle(SRP) and consider controller as coordinator between the view and model, in which case it will contain minimum logic.

5. Service vs Factory

These naming embarrasses every AngularJS newbie, although in reality they are almost the same. Let’s look at AngularJS source code:

function factory(name, factoryFn) { 
    return provider(name, { $get: factoryFn }); 
}

function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
      return $injector.instantiate(constructor);
    }]);
}

 

The function service simply calls the factory function, in which provider call is wrapped. If a service calls the factory, what is the difference between them? The thing is $injector.instantiate, within which $injector creates a new instance of the service constructor function. An example of service and factory, performing the same action:

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";
        }
    }
});

 

At a time when helloWorldService or helloWorldFactory will be injected into the controller, both of them will have the only method that returns «Hello World». Since all providers are singletons, we will always have only one instance of the service and one of the factory. So why there are both factories and services, if they perform the same function? Factories provide more flexibility as they can return a function that can create new objects. In OOP, the 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 the controller using the 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 may also be useful in the development of 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. Batarang disusage

Batarang is an extension of the Chrome browser for the development and debugging AngularJS applications. Batarang allows you to:

  • view model attached to Scope
  • build a dependency graph in the annex
  • analyze application performance

Despite the fact that the AngularJS performance is good “from the box”, when app increases with the addition of custom directives and complex logic, application can start lagging. Using Batarang ease understanding which function is spending a lot of time in the call. Batarang also displays the watch tree, which can be useful when using a large number of watchers.

7. Too many watchers

As noted above, AngularJS has good performance. However, when the number of watchers reaches 2000, $digest cycle, in which the verification information changes, may slow down the application. Although the achievement of 2000 does not guarantee the delay, it is a good starting point from which you can already start to worry. The following code can be found on the page number of observers:

(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 Bataranga watchers tree, you can see if you have watchers-duplicates or watchers of immutable data. In the case of immutable data, you can use a bindonce directive, to not increase the number of watchers on the page.

8. Scope Inheritance ($scope’s)

Inheritance in JS-based prototype differs from the classical inheritance on classes. This is usually not a problem, but these details may appear when working with Scope. In AngularJS normal (non-isolated) $scope is inherited from the parent to the oldest ancestor of $rootScope. Common data model, shared Scope parent with a child, easily organized through inheritance prototype. In this example, we want the user name to simultaneously display two elements span, 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 a user enters his name in the text field, in which elements of it will be displayed: navCtrl, loginCtrl, or both? If your answer is – loginCtrl, you understand how inheritance works based on prototypes. Looking for string fields the prototype chain is not used. To achieve the desired behavior, we use the object, it is desirable to properly update the user name in the child and the parent $scope. (Remember that in JS functions and arrays are 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, because of the variable user – the object prototype chain will work and span in navCtrl will be properly updated with loginCtrl. It may look unnatural, but when dealing with directives creating a subsidiary scope (like ngRepeat), these moments will occur.

9. Using the test-driven development

As long as you do not start using TDD in your work, you have to run project every time and carry out manual testing to make sure that your code works. There is no justification for the use of this approach in the case of AngularJS. AngularJS was originally designed to keep its code testiable. DI, ngMock – your best assistants in this. Also, there are several tools that can help you move to the next level.

9.1 Protractor

Unit tests are base for constructing a fully coated test applications, but with an increase in integration tests using the project may be more efficient to check whether the application code in viable. Fortunately, the AngularJS team has developed a wonderful tool – Protractor, that is able to mimic the interaction with the user. Protractor uses Jasmine framework for writing tests and has a good API for descriptions of the various interaction scenarios. Among the many different tools for testing, Protractor have an advantage in understanding the internal AngularJS structure, which is especially useful when you’re dealing with something like $digest cycles.

9.2. Karma

The AngularJS project team not limited to write tools for test development. The team also developed test runner Karma. Karma enables tests every time you change a source file. Karma is able to perform parallel tests in multiple browsers. Different devices may also be aimed on the Karma server for a more complete coverage of real scenarios.

10. Using jQuery

jQuery is a wonderful library. It standardizes the cross-platform development and has become the standard in modern web development. Despite the fact that jQuery has a lot of features, it is far from the philosophy of AngularJS. AngularJS is a framework for building applications, while jQuery is just a library that simplifies the process of interaction between JavaScript and HTML, and provides a convenient API for working with AJAX. This is a fundamental difference between them. Angular is an approach to building applications, and no way to control the markup document. To really understand the principles of AngularJS applications, you should stop using jQuery. JQuery makes you conform to existing HTML standard, while Angular allows you to expand the HTML standard to the needs of your application. DOM manipulations in AngularJS should be made in the guidelines, but it is quite possible to place wrappers of the existing jQuery components in the directives, if you could not find an analogue in Angular.

Conclusion

AngularJS – is a magnificent, constantly improving framework with excellent community. I hope my list of popular mistakes will be useful for you.