AngularJS Overview and Best Practices

Agenda

  1. Core Angular Features
  2. Data Bindings
  3. Controllers
  4. Services and Dependency Injection
  5. Directives
  6. Filters
  7. Best Practices 
  8. Working with .NET MVC

Core Angular Features

  • Data Bindings (One and Two Way Bindings)
    • INotifyPropertyChanged (Observers) vs. Digest Cycle
  • Main Elements:
    • Controllers
    • Factories
    • Directives
    • Filters
    • Dependency Injection

Data Bindings

  • Synchronization of data between the view and the model
  • One Way / Two Way / (Three Way)

Data Bindings

One Way

<!DOCTYPE html>
<html ng-app>
<head>
    <script src="angular.js"></script>
</head>

<body ng-init="firstName = 'Peter'; lastName = 'Pan';">
    <strong>First name:</strong> {{firstName}}<br />
    <strong>Last name:</strong> <span ng-bind="lastName"></span>
</body>
</html>

Data Bindings

Two Way

<!DOCTYPE html>
<html ng-app>
<head>
    <script src="angular.js"></script>
</head>

<body ng-init="firstName = 'Peter'; lastName = 'Pan';">
    <strong>First name:</strong> {{firstName}}<br />
    <input type="text" ng-model="firstName"><br /><br />
    <strong>Last name:</strong> <span ng-bind="lastName"></span><br />
    <input type="text" ng-model="lastName">
</body>
</html>

Data Bindings

Lists

<!DOCTYPE html>
<html ng-app>
<head>
    <script src="angular.js"></script>
</head>

<body ng-init="persons = [{'name': 'Person 1'}, 
{'name': 'Person 2'}, {'name': 'Person 3'}]">
    <ul>
        <li ng-repeat="person in persons">
            <span>{{person.name}}</span>
        </li>
    </ul>
</body>
</html>

Controllers

  • Control the data of AngularJS applications
  • Glue between the View and the Data
<div ng-controller="PersonController as pc">
    First Name: <input type="text" ng-model="pc.firstName"><br>
    Last Name: <input type="text" ng-model="pc.lastName"><br>
    <br>
    Full Name: {{pc.firstName + " " + pc.lastName}}
</div>
(function(){
    'use strict';
	
    angular.module('person').controller('PersonController', ['$scope', function ($scope) {
        $scope.firstName = 'Peter';
        $scope.lastName = 'Pan';
    }]);
})();

Services and Dependency Injection

  • Share information across your application
  • helpful Interfaces

Dependency Injection =  Takes control of creating components, resolving their dependencies, and providing them to other components as requested

Services and Dependency Injection

(function(){
    'use strict';
	
    angular.module('person')
        .controller('PersonController', ['$scope', 'persons', 'notifications' 
            function ($scope, persons, notifications) {
                $scope.persons = persons.getAll().then(function(response) {
                    notifications.success('Persons loaded.');
                }, function(error) {
                    notifications.error('An error occured while loading the data.');
                });
            }
        ]);
})();

Services and Dependency Injection

(function(){
    'use strict';
	
    angular.module('person')
        .factory('persons', ['apiService', function (apiService) {
            // Interface
            var persons = {
                getAll: getAll,
                get: get,
                add: add
            };

            return persons;        

            // Implementation
            function getAll() {
                return apiService.call('GET', '/persons/getAll');
            }
        ]);
})();

Services and Dependency Injection

Factory vs. Service

  • Factory gets invoked and cached
  • Service gets new-ed (constructor)
  • It is up to you what you use

Directives

  • Extend HTML with new elements or attributes
  • Can be seen as components
  • Parts:
    • Attribute or set that we use to declare an HTML node
    • JavaScript that implements the functionality

Directives

(function(){
    'use strict';
	
    angular.module('person')
        .directive('personList', [function () {
            return {
	        restrict: 'AECM', 
                replace: true, // make sure there is only one top level node
                transclude: true,
                controller: function($scope, $element, $attrs),
                require: 'ngModel', // ^, ?
		link: function($scope, $element, $attributes, controller),
		scope: {
		    data: '=' // @, =, &, ? e.g. =? or =?my-data
		},
                template: '<div>Test</div>', // or as function
                templateUrl: './test.html', 
                compile: function(tElement, tAttrs), // pre-link, post-link
                priority: 0 // default
	    };
        ]);
})();

Directives

<span person:list="data"></span>
<span person_list="data"></span>
<span person-list="data"></span>
<span data-person-list="data"></span> // Best Practice
<span x-person-list="data"></span>

// or as an element

<person-list></person-list> // no support for IE8 and below
  • Directive name in camelCase can be used as:

Directives

  • Built-In Directives:
    • ngModel
    • ngShow
    • ngHide
    • ngRepeat
    • ...

Filters

  • Filters can be added to expressions and directives using a pipe character
  • Data Transformation
    • currency
    • filter
    • lowercase
    • uppercase
    • orderBy

Filters

Search: <input ng-model="searchText">
<table id="searchTextResults">
    <tr><th>Name</th><th>Phone</th></tr>
    <tr ng-repeat="friend in friends | filter:searchText">
        <td>{{friend.name}}</td>
        <td>{{friend.phone}}</td>
    </tr>
</table>
Name only <input ng-model="search.name"><br>
Phone only <input ng-model="search.phone"><br>
Equality <input type="checkbox" ng-model="strict">

<table id="searchObjResults">
    <tr><th>Name</th><th>Phone</th></tr>
    <tr ng-repeat="friendObj in friends | filter:search:strict">
        <td>{{friendObj.name}}</td>
        <td>{{friendObj.phone}}</td>
    </tr>
</table>

Filters

(function(){
    'use strict';
	
    angular.module('common.filters')
        .filter('reverse', [function () {
            function reverse(input) {
                input = input || '';
                var result = "";

                for (var i = 0; i < input.length; i++) {
                    result = input.charAt(i) + result;
                }

                return result;
            }

            reverse.$stateful = true; // executes each digest cycle

            return reverse;
        ]);
})();
<div>{{ lagerregal | reverse }}</div>

Best Practices

  • Define one component per file
  • Use IIFEs (Immediately Invoked Function Expressions) to wrap AngularJS components

 

 

 

 

(function() {
    // prevents from certain actions from being taken and throws more exceptions.

    'use strict' 

    app.module(...
})();

Best Practices

  • Use controllerAs Syntax also in directives
<div ng-controller="ParentController as pc">
    {{ pc.name}}

    <div ng-controller="ChildController as cc">
        {{ cc.name }}
    </div>
</div>
  • Declare variables always at the top
return {
    restrict: 'EA',
    scope: {
        value: '='
    },
    link: linkFn,
    controller: ExampleController,
    controllerAs: 'vm'
};

Best Practices

  • Use named functions over anonymous functions
    • more readable code
(function() {
    'use strict';

    angular.module('dashboard').controller('Dashboard', Dashboard);

    dataservice.$inject = ['$http', 'logger'];

    function Dashboard($http, logger) { }
})():
  • Keep Controllers focused
    • define per concern
    • move reusable logic to common services

Best Practices

  • Use function declaration to hide implementation details
    • keep accessible members up top and assign functions
(function() {
    'use strict';

    angular.module('dashboard').factory('persons', persons);

    function persons() {
        // Interface
        var persons = {
            getAll: getAll
        }

        return persons;
    
        // Implementation

        function getAll() {}
    }
})():

Best Practices

  • Seperate data calls in seperate services (e.g. ApiService)
  • Use promises for data calls (Callbacks 2.0)
    • No Pyramid of Doom (clearness)
    • Synchronization ($q.all)
$http.get('/Checklist/Index')
  .then(function(response) { return transform1(response) })
  .then(function(response) { return transform2(response) })
  .then(function(response) { return transform3(response) });

Best Practices

  • Restrict directives to E (Element) and A (Attribute)
  • Abstraction by Modularization
    • Create modules for concerns
  • No DOM-Manipulation in Controllers
  • Business Logic belongs to services
    • Makes it easy to share among different controllers and other services
  • Reduce Code Redundancy by abstracting common functionallity to services (e.g. ApiService)

Best Practices

  • data attribute for valid HTML
<div data-ng-controller="ParentController as pc">
   <div data-my-directive>
        <span>Text goes here</span>
    </div>
</div>
  • Declare specific Model-Factories (in this case ViewModels)
    • .NET ViewModel changes need to be copied to the corresponding angular models

Working with .NET MVC

  • Frontend (AngularJS) is seperated from the Backend (.NET Controllers and Models)
  • Interaction between Frontend <=> Backend via JSON
    • .NET ViewModels will be serialized and mapped to angular models
  • Frontend SPA Routing can / will be handled by AngularJS
  • .NET Views will no longer be needed
  • Validation: no jQuery

Example Directives

  1. Develop a directive which creates a simple WYSIWYG Editor

2. Enhance the WYSIWYG directive so that values in ngModel 

    are parsed and formatted as markdown

  • The View expects raw HTML and displays raw HTML
  • The models saves the HTML as markdown

Example Directives

3. Develop a directive with a Datepicker?

  • Pass the date format as an attribute
  • Enhance the directive so that the format can be selected with a <select>

ng-neusta

By Dominic E.

ng-neusta

  • 720