Evaluation of AngularJS, a JavaScript UI Framework

by Ilya DrabeniaAugust 29, 2013
The blog post overviews the main AngularJS features, as well as provides tips on how to fix some issues while developing web applications.

Rapid development

During the last two months, the team at Altoros has been using AngularJS, a JavaScript UI framework, to develop a user interface for the next-generation document management system. The experts also utilized Grunt for building a client application and Karma for automating unit tests. The back-end part was developed with the Java technology stack.

With this tool, JavaScript development becomes really fast. A single person can bring out 1–2 significant features a day. Not less important, AngularJS is also good for prototyping. This framework makes it possible to find the right balance between code quality and velocity of the development process.

If you are a novice to AngularJS, you can start with this introduction article. This article provides a short overview of the main AngularJS features and shares tips on how to fix some issues.

 

Extending HTML capabilities with Directives

AngularJS comes with a set of built-in directives that allow for creating custom HTML elements and modifying the behavior of DOM elements. It can be quite useful for building complex front ends. Isolated scopes enable developers to divide data and use a particular portion of this data when necessary. One of the main documentation drawbacks is that it provides just a few examples of how to work with directives and scopes. So, we had to use these components intuitively.

However, you should be very careful while playing with them, since they may cause a lot of issues. For instance, the documentation says that a directive scope in AngularJS may be a child to the parent scope through the prototype-based JavaScript inheritance. In this case, parent scope variables will only be available for reading. Our team spent quite a lot of time investigating different parts of the application to find out how it really works. Eventually, we discovered a very useful tool for resolving issues like that—AngularJS WebInspector Extension for Chrome. It is a browser plug-in that allows for navigating through the hierarchy of AngularJS scopes.

 

Testing

The testing of applications in AngularJS is simple and intuitive.

Unit tests

We created the following unit tests to check the system’s models.

describe(‘Tabs control’, function() {
    var model;

    beforeEach(module(‘mycontrol’));
    beforeEach(inject(function(MyControlModel) {
         model = new MyControlModel();
    }));

    it(‘should allow to add new element’, function () {
         model.add({name: ‘Header’});
         expect(model.elements.length).toBe(1);
    });
});

 

Integration tests

AngularJS unit test libraries enable to create integration tests as well. As for our system, we developed a directive that represented a page and then triggered the events, using jQuery. After that, the markup and state of the models were verified. We mocked the calls to the REST API with the “$httpBackend” service.

With this approach, smoke tests were completed just in five seconds. However, some specific features of Jasmine, a behavior-driven development framework for testing a JavaScript code, became a stumbling block. As it turned out, it does not allow for creating a single fixture per test suite. This feature is required to reduce the duration of tests on big components. To overcome this limitation, we created all tests in a single “it” function.

describe(‘Tabs control’, function() {
    var control;
    var scope;

    beforeEach(module(‘mycontrol’));
    beforeEach(inject(function($compile, $rootScope, $httpBackend) {
         // create custom directive with model
         scope = $rootScope.$new();
         control = $compile(‘<control></control>’)(scope);

         // mock some backend calls
         $httpBackend
             .when(‘GET’, ‘/api/resources’)
             .respond([{name: ‘name1’, data: ‘other_data’}]);
    }));

    it(‘should allow to add new element’, function () {
         $(control).find(‘button.add’).click();
         expect($(control).find(‘table tr’).length).toBe(1);
    });
});

 

End-to-end tests

As it was previously mentioned, we used Grunt to build a client. It has the “watch” feature that starts tests, when some changes are made to the code. To test the application on servers that have no graphical user interface, PhantomJS was used. All the technologies described above are very efficient together.

Another AngularJS feature is black-box testing of user interfaces with JavaScript. However, we decided to skip it. If used it, we would need to create a separate web page with the mocked back end to carry out these tests. In our case, it would make the back-end part of the system more complicated. Furthermore, we would be unable to distinctively separate the test code from the production code.

 

Filtering

AngularJS is very declarative and functional. Pure functions, closures, and the currying method are widely used when working with this framework.

The template engine introduces a new intriguing concept of filters. It resembles map functions with the syntax inherited from Linux shell streams. With the two-way binding of scopes, this feature allows for implementing amazing functions very fast. These filters enabled us to easily create lookups—reusable lists of key-value pairs in drop-down menus.

filter(‘lookup’, [function () {
    var LOOKUPS = {
        USERS: {
            NEW: ‘New User’,
            EDIT: ‘Edit User’
        }
    };
    return function (input, lookupName) {
        return LOOKUPS[lookupName][input];
    }
}])

<div ng-init=”foo = ‘NEW’”> <!-- Key of lookup value -->
{{ foo | lookup:’USERS’ }} <!--  Will be rendered ‘New User’’ -->
</div>

 

Dependency injection

AngularJS has a built-in dependency injection (DI) engine. Compared to pure jQuery, AngularJS provides better isolation of modules, as developers should spend less time reviewing the legacy code after adding new features. With this pattern, we were able to minimize the impact of the changed code on the entire system.

However, the DI engine may be not suitable in some cases. For instance, to create a constructor, we needed to develop a separate module, give it a name, and specify this name in the code each time the constructor was used. It is much more time-consuming than just to create a namespace that will automatically insert a constructor everywhere we need it. We tried to find a way to bring this functionality to the AngularJS DI engine but did not come across an acceptable option.

 

Form validation

Validators usually need customization. Since forms and validators are very flexible in AngularJS, you will be able to create an editable table with minimum efforts. Below is a simple example of a form validation.

<form ng-app="miniapp" name="miniform" novalidate> 
    <div ng-controller="Ctrl">
        <input name="field" type="text" ng-model="value" number="" />
        <span ng-show="miniform.field.$error.number">Field must contains number</span>
    </div>
</form>

angular.module('miniapp', [])

    .controller('Ctrl', function ($scope) {
        $scope.value = '';
    })

    .directive('number', function () {
        return {
            require: 'ngModel',
            link: function (scope, elm, attrs, ctrl) {
                ctrl.$parsers.unshift(function (viewValue) {
                    var isEmpty = viewValue === '';
                    var isNumber = !isNaN(parseInt(viewValue, 10));
                    ctrl.$setValidity('number', isEmpty || isNumber);
                    return viewValue;
                });
            }
        };
    });

You can play with this code on jsFiddle.

 

The template engine

Although the engine supports HTML tags, we experienced certain issues with some of the tags while working with custom directives.

Another issue is that some standard AngularJS tags create new scopes, since a parent scope is only accessible while reading.

In addition, it is not that easy to develop custom directives. AngularJS does not support directive inheritance, as a result, the code is duplicated. Nevertheless, the composition of components looks really great.

 

Integrations and compatibility

Sometimes it can be tricky to integrate AngularJS with jQuery plug-ins. We experienced memory leaks and spent a lot of time on fixing this. However, there is a way to easily eliminate these leaks. You just need to delete jQuery plug-in variables when deleting AngularJS scopes.

The front end of our system was developed for IE8. To provide support for earlier versions of IE, we used the html5shiv script.

 

Conclusion

Being well-architected, AngularJS speeds up front-end development and provides almost enterprise-level testing capabilities. This framework has better memory management than Backbone.js, supports form validation that Ember.js lacks, and allows for more flexible customization of components compared to Ext JS. With AngularJS, it will be easy for you to properly organize your web applications.

 

Further reading