Friday 15 April 2016

AngularJS Coding review

This article lists some of the best practices that would be useful for developers while they are coding with AngularJS. These are out of my own experiences while working on AngularJS and do not warranty the entire list. I am sure there can be more to this list and thus, request my readers to suggest/comment such that they could be added to the list below. Found some of the following pages which presents a set of good practices you would want to refer. Thanks to the readers for the valuable contribution.

Initialization

  • One should try and place the <script> tag including the angular.js and related angular scripts at the bottom of the page to improve the app load time. This is because the HTML loading would then not be blocked by loading of angular.js and related scripts.

Expressions

  • Complex javascript code should be made as a method in the controller (added as a method to the $scope) and then, should be called from the view. This is unlike putting the complex Javascript code as Angular expressions, right, in the view.

Controllers

  • With real applications, one should avoid creating controllers in the global scope. Rather, one should use “.controller” method of the Angular Module to work with $scope object. Take a look at the sample code below illustrating this point:
Controller defined in the Global Scope:
function HelloController( $scope ) {
$scope.name = "Guest";
}
Controller defined using “.controller” method (Recommended way)
var helloApp = angular.module( "helloApp", [] );
helloApp.controller( "HelloController", function($scope){
$scope.name = "Guest";
} );
  • The controllers should only be used to setup initial state of the $scope object and add one or more behaviour to this object. One should avoid using controllers to do some of the following:
    • Manipulate DOM, 
    • Format input, 
    • Filter output, 
    • Share code or state across different controllers
    • Manage the life-cycle of other components (for example, to create service instances)
  • A controller should contain only the business logic needed for a single view. The functionality should rather be moved to services and these services should be injected into the controllers using dependency injection.
  • The recommended way to declare the controller function is to use the array notation such as following because it protects against minification.
Controller instantiation without array notation
var helloApp = angular.module( "helloApp", [] );
helloApp.controller( "HelloController", function($scope){
$scope.name = "Guest";
} );
Controller instantiation with array notation (Recommended way)
var helloApp = angular.module( "helloApp", [] );
helloApp.controller( "HelloController", ['$scope', function($scope){
$scope.name = "Guest";
}]);
  • While writing unit tests for controllers, one of the recommended ways is to inject $rootScope & $controller. Take a look at the sample unit tests on this page: http://hello-angularjs.appspot.com/angularjs-unit-test-code-example-1. The following is the sample code representing injection of $rootScope and $controller objects.
    beforeEach(
    inject(
    function( $rootScope, $controller ){
    scopeMock = $rootScope.$new();
    $controller( 'CompanyCtrl', {$scope: scopeMock} );
    }
    )
    );

Directives

  • One should prefer using the dash-delimited format (e.g. ng-model for ngModel). While working with an HTML validating tool, one could instead use the data-prefixed version (e.g. data-ng-model for ngModel).
  • Prefer using directives via tag name and attributes over comment and class names. Doing so generally makes it easier to determine what directives a given element matches.
  • While creating directives, it is recommended to prefix your own directive names to avoid collisions with future standard.

Template

  • With large templates (HTML content with Angular directives) within an HTML file, it is recommended to break it apart into its own HTML file and load it with the templateUrl option.

Dependency Injection

The preferred way of injecting the dependencies is by passing the dependency to the constructor function rather than using one of the following other ways. In this way, the responsibility of creating the dependency object lies with other objects or function. This is straight forward for those who have worked with one or more dependency injection framework in the past. However, this could be useful information for the beginners. Following are other ways of doing dependency injection in Angular:
  • Create it using the new operator.
  • Look for it in a well-known place, also known as a global singleton.
  • Ask a registry (also known as service registry) for it.

Unit Testing

  • As mentioned above, one should try and avoid using controller methods for DOM manipulation. That would bring views code inside the methods and make it difficult to test.
  • One may want to use Jasmine/Karma combination for doing controller methods testing.
  • Protractor framework can be used for E2E testing, as recommended. Read more on Angular page for E2E testing.

The Web Dev Zone is brought to you by Stormpath—offering a pre-built, streamlined User Management API for building web and mobile applications. Plan On Building User Management? Buy It Instead.Download Our White Paper To Learn More.

This is the third part of the AngularJS by Example series. If you have missed the previous installments you can find the firsthere and the second here.
AngularJS by Example - Implementing AngularJS controllers using best practices
Controllers in AngularJS allow you to create an isolated scope and “control” data before it is passed to the view. A style guide by John Papa which you might have already read provides best practices for a wide range of Angular concepts. If you’ve not read it yet, you really should. In this style guide there are many good points about the best ways to implement controllers and I have adopted these recommendations in the AngularJS by Example app. This tutorial will use examples to illustrate the different controller implementation methods and provide insight into why the recommended method was chosen.
 Info
With the announcement of AngularJS 2.0 and its lack of controllers, its a very good idea to keep your controllers as light as possible making it easier when the time comes to migrate.

Traditional method using $scope

Using a controller you are able to define an isolated scope and then control what part of your application has access to this scope and therefore the data bound to it. The traditional method for implementing controllers is to inject a new scope instance using the $scope variable and then bind data to it. You will then be able to access this bound data within the view where your controller has governance. The below example illustrates this basic principle.
Here you can see two variables bound to the controllers $scope object. Within the HTML, ng-controller is then used to specify what part of the application the controller is responsible for.
<body ng-app='app' ng-controller='MainController'>
    <div class='container'>
        <h1>{{pageTitle}}</h1>
        <p>{{pageDescription}}</p>
    </div>
</body>
After this anywhere within the element which has ng-controller='[CONTROLLER NAME]' declared, scope variables from that controller are then available using double curly braces as shown in the above example.
This method provides the simplest way to link your data with the view. You also have some additional benefits as the $scopeobject provides various functions available for use in your controllers. These include some of the following:
  • $scope.$watch() - To watch for changes on scope properties
  • $scope.$watchCollection() - Similar to watch except with a shallower comparison for increased performance
  • $scope.$eval() - Evaluates an expression against the scope
  • $scope.$emit() - Emit custom events
  • $scope.$on() - Listen for and act on events
These are just a few examples of the useful functions available when injecting the $scope object into your controller. Head over the the documentation for more detail on what methods the $scope object provides.

Downsides of binding data to $scope

Using $scope looked really simple, so why do anything different? There are a few downsides to using the traditional implementation.

Scope bleed

In large applications using the traditional method it is very easy to accidentally overwrite scope variables and have adverse effects in you views. The following example demonstrates this.
This example has two simple controllers where SubController is nested under the MainController which is a common occurrence in many AngularJS applications. The main controller has both my first name and last name assigned to its scope so I can output this to the view. The issue is that I have accidentally used the same variable name within the SubControllerand overwritten the firstName variable in the MainController. When my name is output to the view it now appears wrong! This might seem like a silly example but it is so easy for this to happen in larger applications with many scope variables defined.

Confusion in the view

Using the traditional method it can be difficult to understand where your view variables are originally declared if you have multiple controllers being used in the same view. With the simple example above you wouldn’t know that firstName andlastName are declared within the MainController or SubController without looking. Imagine you have even more controllers and many similar variable names. This quickly becomes a real headache.

The recommended method using ControllerAs

AngularJS provides some additional functionality for when declaring where your controllers are used, this additional functionality is the controllerAs method.The following example shows how to use the ControllerAs syntax.
 Info
You can also use the ControllerAs syntax when defining your routes, see a later installment of this series or take a look at the documentation.
The first difference with the example above is the use of the as keyword within the ng-controller declaration highlighted below.
<body ng-app='app' ng-controller='MainController as main'>
    <div class='container'>
        <h1>{{main.title}}</h1>
        <div ng-controller='SubController as sub'>
            <h2>{{sub.title}}</h2>
            <p>If we were not using the <strong>ControllerAs</strong> syntax we would have a problem using title twice!</p>
        </div>
    </div>
</body>
Using the as keyword we are able to provide an alias for each of the controllers. In the above example our MainControlleris now aliased to main and our SubController has an alias of sub. For us to then be able to access the controller variables within our views bind our scope data to the controller instance using the this keyword.
angular.module('app').controller("MainController", function(){
    var vm = this;
    vm.title = 'AngularJS Nested Controller Example';
});
In the above code sample you will notice the missing injected $scope variable. Instead of the $scope variable provided by Angular we simply declare a vm variable (which stands for view model) and assign this to it (i.e. the instance of the controller function). All variables will now be assigned to the vm object instead of $scope. With our variables assigned to the instance of the controller function we are then able to access the variables within the view using the alias.
<body ng-app='app' ng-controller='MainController as main'>
    <div class='container'>
        <h1>{{main.title}}</h1>
        <div ng-controller='SubController as sub'>
            <h2>{{sub.title}}</h2>
            <p>If we were not using the <strong>ControllerAs</strong> syntax we would have a problem using title twice!</p>
        </div>
    </div>
</body>
The above example also illustrates how ControllerAs solves the scope bleed issue that was mentioned earlier. As both the controllers now have meaningful aliases we are able to use the same variable name title without overriding a parent variable. When you have multiple nested controllers like this the ControllerAs method ensures your view code is more semantic and easier to understand.

this vs vm

You might be wondering why you have to bother assigning this to the vm variable. You can in fact just use this skipping the use of vm completely as the following example illustrates, but there are issues with this method (more on this shortly).
angular.module('app', []);

angular.module('app').controller("MainController", function(){
    this.title = 'AngularJS Nested Controller Example';
});

angular.module('app').controller("SubController", function(){
    this.title = 'Sub-heading';
});
The problem with just using the this keyword is that it is contextual to its wrapping function. In the following examplethis would refer to the instance of the doSomething function and not the controller function as a whole.
angular.module('app').controller("SubController", function(){
    this.title = 'Sub-heading';
    this.doSomething = function() {
        this.test = 'Test 123';
    };
});
To ensure we are always referring to the same scope we assign the vm variable at the top of our controller.

What about those useful $scope methods?

As previously mentioned you wont have access to the $scope methods if you haven’t injected it into your controller. The answer is simple, inject $scope where and when you want access to those methods.
angular.module('app').controller("SubController", function($scope){
    var vm = this;
    vm.title = 'Sub-heading';
    $scope.$on('customEvent', function(){
        alert('My custom event was fired!');
    });
});

Conclusion

This tutorial has provided insight using examples into the different ways you can create controllers for your AngularJS application. It provides a case about why you should adopt the ControllerAs method as opposed to the traditional method of binding all variables and functions to the $scope object.
Controllers are used within other areas of an Angular application such as directives which will be covered in detail in a later addition to the AngularJS by Example series.
To ensure you don’t miss out follow me on Twitter (@RevillWeb) or subscribe below to be the first to know when the next tutorial in the series is available. I’m always interested in feedback so please get in touch if you have something to share.

No comments:

Post a Comment

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...