AngularJS for newbies

17 Oct 2016

A few days ago Angular was announced in the final version 2.0.0. Time to make a review on the version AngularJS 1.x and on some best practice examples.

Architecture


The MVC* model

The AngularJS architecture use the MVC (Model View Controller) design pattern, a two-way data binding and dependency injection. Unlike some other MVC frameworks AngularJS extends the HTML code but, instead of abstracting it. With the MVC pattern, the application logic is separated from the user interface layer. The controller get all requests from the application and worked with the model to prepare the data for the view. The view use all these data to present them to the user.

The Model:
The model is responsible for managing application data. It responds to the request from view and to the instructions from controller to update itself.

The View:
A presentation of data in a particular format, triggered by the controller's decision to present the data.

The Controller:
The controller responds to user input and performs interactions on the data model objects. The controller receives input from the injected factory service and then performs business operations that modify the state of the data model. With the two-way data binding any changes on the view layer could be detected in the controller.

The Service:
An additional layer will be archived by use a factory service to fetch the data from the back-end system. In the factory service the data will be validated, enriched and hold. This will make the controller lighter and improve performance. A target is here to make the controller as small as possible. In combination with Dependency Injection, the factory services that called the back-end system, is used in the controller by inject them.

AngularJS 1.x

To make managing a portion of an HTML page of AngularJS, this must be enclosed in an element with the attribute "ng-app". After loading the page index.html, AngularJS searches for the element "ng-app" and analyzes thereof enclosed area. The application will be bootstrapped by detected this area.
<body id="myapp" ng-app="app" class="full-height">
   <div class="full-height">
      <div class="header" ui-view=""></div>
      <div class="menu"   ui-view="menu"></div>
      <div class="split"  ui-view="splitter"></div>
   </div>
   …
</body>
Now the ng-module concept comes into the focus. The ng-module is a global place for creating, registering and retrieving Angular modules. All modules that should be available to an application must be registered using this mechanism. A module is a collection of services, controllers, filters, directives and configuration information. Ng-module is used to configure the dependency injection part. We named the ng-app “app”, so angular is search in the javascript module files this name.

Ng-module is also used to inject foreign packages. In our programs we inject the ui-router service, toastr, angular material, underscore and Kendo UI (alternative Google charts).

In our application the next element is now the ng-run, these element is the initial start point for the application. We use these point to start the current state of the application with the ui-router part. Our application knows at this construction point only one state (or if you will to say, only one page “detailview”). But the ui-router part has also other issues, because it separates parts from each other, make them simpler and bring some performance improvements.

With the design of three parts (header, menu and detail) we get a flexible state based concept that allow us to create several pages in one structure in combination with the router concept.



Every element could now be defined separated in the config file of the router, so that based on the state of the application, several pages could be displayed. The combination allow us now to define per view a html sub-page and a controller for this part.
   .state('tileview', {
                url: '/',
                views: {
                    '': {
                        templateUrl: './js/header/header.html',
                        controller: 'headerController',
                        controllerAs: 'hc'
                    },
                    'menu': {
                        templateUrl: './js/menu/menu.html',
                        controller: 'menuController',
                        controllerAs: 'mc'
                    },
                    'detail': {
                        templateUrl: './js/detail/detail.html',
                        controller: 'detailController',
                        controllerAs: 'dc'
                    }
                }
            })
        
Controller are defined in AngularJS using the property "ng-controller". Controller and view communicate via the "scope", a container can be accessed on its contents from both sides. With the "{{}}" notation can be accessed by means of a one-way binding in HTML on elements of the "scope". The data can be displayed, but not restored. Two-way bindings allow writing in the "scope" and logically can only be used on input elements. To get the data from the back-end system, we inject factory services in the controller.

The ng-provider service factory is the next main part of the concept. These services are singleton objects and are functions which, in turn, are created by a service provider. The service providers are constructor functions. When instantiated they must contain a property which holds the service factory functions.

Components Folder

The components folder will contain the actual part of the angular app. So you could think of this like a multiple mini AngularJS application inside of your AngularJS application.
In this example we describe the header section. The header part is separated into four files and for simple collection reasons separated in an own folder named “/header”. So for every part of the application we get components folders, these folders holds all necessary information for a component.

header.html The html page. The scope is presented by “hc” (headerController).
header.module.js Makes a module for this part, so that is could be injected by other element and factual holds other modules for a deeper nesting.
header.controller.js The controller, based on the same module name.
header.service.js The factory service, based on the same module name and injected in the controller. This service fetches data from back-end system and prepare data for the controller.


The folder structure of the app looks like this:

js/ header/



menu/



detail/



common/
header.html
header.module.js
header.controller.js
header.service.js
menu.html
menu.module.js
menu.controller.js
menu.service.js
detail.html
detail.module.js
detail.controller.js
detail.service.js
service/

directive/
filter/
constant












message.service.js
...
...
...
...
...

Implementation

No we have a look at some of the key features of AngularJS.

Cleancode

When you work in a team, it is easier to use a style that is accepted from all over the world. My code examples based on the clean code style guide from John Papa (@John_Papa ) for AngularJS.

Component Design Example: header

The following code examples shows the combination of the header part. Through the loose coupling between the areas, we can get easy to maintain system.

Header module (header.module.js)

The module name for this area is “header”. The controller and the service factory get the same module name for this area and could be loaded as package when needed.
(function () {
    'use strict';
    angular.module('header', [
    ]);
})();

Service factory module (header.service.js)

The service factory get the initialization during the injection in the controller. The factory send back the available services. In this example we inject the $http service. The $http service is a standard function from AngularJS. $http returns a promise so we can return back the promise immediately. This means that the “.then” part runs after the result from the back-end systems arrived.
(function () {
    'use strict';

    angular
        .module('header')
        .factory('headerService', headerService);

    headerService.$inject = ['$http', '$q'];

    function headerService($http, $q) {
        var service = {
            user: [],
            client: [],
            loadHeaderData: loadHeaderData,
        };
        return service;

        ////////////

        function loadHeaderData() {
            return $q.all([loadUserData(),loadClientData()]).then(function (result) {
                service.user = result[0];
                service.client = result[1];
            });
        }

        function loadUserData() {
            return $http.get('../js/header/user.json');
        }

        function loadClientData() {
            return $http.get('../js/header/client.json');
        }
    }
})();

Controller module (header.controller.js)

The controller get now the factory service injected. An asynchronous call was started during the start process and will be finished when the promises get a result from the back-end system.
(function () {
    'use strict';

    angular
        .module('header')
        .controller('headerController', headerController);

    headerController.$inject = ['headerService', 'messageService'];

    function headerController(headerService, messageService) {
        var hc = this;

        hc.user = [];
        hc.reload = reload;
        hc.spin = headerService.spin;
        loadData();
        
        /////////
        function reload() {
            messageService.addMessage('reload', 'reload', null, null);
        }
       
        function loadData() {
            headerService.loadHeaderData().then(function () {
                hc.user = headerService.user;
            });
        }
    }
})();
In our code example we used simple json structure for the client.json
{
    "client": "D01",
    "id": "11"
}
and for the user.json.
{
    "name": "Donald Duck",
    "city": "Duckburg"
}
Both files are stored in the same "header" folder.

Html Part (header.html)

The databinding between Controller and View Model, which was served by the scope (in our case the scope get the name “hc” – headerController) is realized by “{{}}” the two brackeds. In this example hc.user.name holds the information about the user name. “hc” is the scope variable, “user” is the collection and “name” holds the information about the user name.
<div class="pure-g header-box">
    <div class="pure-u-14-24 header-pos">
        <label  ng-if="::hc.user.name" class="header-user">{{::hc.user.name}}</label>
    </div>

    <div class="pure-u-7-24 header-pos">
    
    </div>

    <div class="pure-u-3-24 header-pos">
        <div class="pull-right pointer" ng-click="::hc.reload();" title="Reload">
            <i class="fa fa-refresh" ng-class="{'fa-spin' : hc.spin}"></i>
        </div>
    </div>
</div>
Ng-if shows the information about the user only, when the collection exists. AngularJS has per default a double binding on the element by “{{}}”, to reduce this binding during the refresh of a page, it is possible to create a single binding by add the both double points “::”. This is a performance issue, and in this case the user information is only one time updated. So it is not necessary to update this information more than once.

$http and Promises

$http service is used to get data from the back-end system. $http give a promise back and this is one of the key factors to use AngularJS, other concept use promises as well and in Angular 2.0 observables are the new concept, but based on the request of the most backend systems, this is one of the killer features of use AngularJS. This is not new and is available in other MVCs as well (but in AngularJS automatic integrated in the main core), but this allows you to build fast and small application in a short time. The old way is to use callbacks, but this often ends in a callbacks nested callbacks, nested inside callbacks … (After this you could not maintain such a system any more).

A promise is a proxy for a value not necessarily known at its creation time. With promises, rather than an asynchronous call accepting a callback, it instead returns a promise. The calling code can then wait until that promise is fulfilled before executing the next step. To do so, the promise has a method named “then”, which accepts a function that will be invoked when the promise has been fulfilled.

Promises helps to run functions asynchronously, and use their return values when they are done processing. There are various ways to combine them. With race conditions, chaining and combining it is possible to run complex based combination of data fetching in the simple way.

In the controller service the data will be updated when they are available from the injected factory service “headerService”.
//controller
loadData();

function loadData() {
            headerService.loadHeaderData().then(function () {
                hc.user = headerService.user;
            });
        }

As result usually a json object is fetched. In the factory service the data will be fetched asynchronously from the back end service.
//factory 
function loadHeaderData() {
         return $http.get("Z_CR_BSP_GET_USER_RUNTIME_DATA")
                .then(function (result){
                   service.user = result;
                });
}
Now when the data returns, the system automatic update the front end part. How easy is this! So this is a non blocking asynchrony code combine with modules in the router. These combination makes it very fast, module oriented and easy to maintain, instead of a callbacks, that nested in callbacks, that nested in callbacks ….

For common failure messages we abstact the $http service in a separate module with the name rest.service.js. This allows us to create common messages when the connection between the front-end part and back-end system fails. What does this mean? Instead of inject the $http service in the factory service of a module and manage the failures there, we inject the common rest.service.js in the factory. So if a failure from the connection between the front-end system comes up, we could there give information to the user on a common module and have not implemented it everywhere.

Ng-controller

There are more options to set a controller for a html file. In the ui-router module the controller was set externally (see ui-router). The other way is used in the html part. Here the controller will set when then html part was called by a part of the code component. In this example we implement this in the chart.html file chart.controller.js.
<div class="charts" ng-controller="chartController as cc">
       ...
    </div>
</div>

Broadcast events

So now we have a loose coupled system, with the maximum on parallelization on back-end request. Between the controllers, we implement a broadcast event, so that on a click on the header part, the tiles used sub-elements could be reloaded.

Therefore we inject the “messageService” in both controllers. With the click event on the html Page ng-click="hc.reload();" in the controller of the header “headerController”, we fire the messageService event.
function reload() {
         messageService.addMessage('emailController', 'reload', null, null);
}
The messageService factory is from type singleton and is only one time initialized.
(function () {
    'use strict';

    angular
       .module('common.service')
       .factory('messageService', messageService);

    messageService.$inject = ['$rootScope'];

    function messageService($rootScope) {
        var service = {
            addMessage: addMessage,
            message: {}
        };

        return service;

        ///////
        function addMessage(receiver, type, value, text, caller) {
            service.message = {
                receiver: receiver,
                type: type,
                value: value,
                text: text,
                caller: caller,
                timestamp: new Date()
            };

            $rootScope.$broadcast('messageAdded');
        }
    }
})(); 
In the controller of an element that should be reloaded (example tileController), we search in the global scope variable for updates, and if a message was received for this controller, we fire the event based on the request. This allow you to combine light message sharing between the controllers. For heavy data loading between two controllers it is better to create an own factory service.
$scope.$on('messageAdded', function () {
            var message = messageService.message;
            if (message && (message.receiver === 'emailController')) {
                switch (message.type) {
                    case "reload":
                        loadData();
                        break;
                }

            }
        });

Ng-repeat

The ng-repeat directive instantiates a template once per item from a collection. Each template instance gets its own scope, where the given loop variable is set to the current collection item. This allows you to repeat given collection of data. In our project this could be tiles or grid combinations. Ng-repeat could be insert in every type of html tag.



Each size of the component grid could be defined with a other html file. This allows a maximum of flexibility and also an easy to maintain the system.

Build process

We combine all AngularJS module files into one big application javascript file. This could be done with gulp process We make then the javascript file available in the index.html file. When combine all javascript parts in one javascript file, it is necessary to bring the elements of the modules (*.module.js) at the first place.

Now have a look at part II of the AngularJS 1.x version. If you are looking for Angular 2.0 example, please check out this.

If you want to check-out one of my applications that was build with angular 1.x and is available in the app store, follow this link.

No comments:

Post a Comment

older post home