Intro into AngularJS

(and then some)

Before we begin

  • Functions are first-class
  • Control flow and hoisting
  • Function-level scopes
  • Closures
  • Global scope
  • Async

Functions are first-class

//Functions do not need to be wrapped by a class
function hello() {
    console.log('Hello world');
}

hello();
//=>Hello world

Functions are first-class

//Functions can return functions
function sayHello() {
    return function() {
        console.log('Hello world');
    };
}

sayHello();
//=> [function]

Functions are first-class

//Functions can be assigned to variables
var speak = sayHello();

speak();
//=>Hello world

Control Flow and hoisting

function sayHello() {
    var name = 'Jack';
    console.log('Hello ' + name);
}

sayHello();
//=> Hello Jack

Control Flow and hoisting

function sayHello() { 
    console.log('Hello' + name);
    var name = 'Jack';
    console.log('Hello' + name);
}

sayHello();
//=> Hello undefined
//=> Hello Jack

function sayHello() {
    var name;
    console.log('Hello ' + name);
    name = 'Jack';
    console.log('Hello ' + name);
}

Control Flow and hoisting

function sayHello() {
    var logToConsole = function() {
        var name = 'Jack';
        console.log('Hello ' + name);
    }

    logToConsole();
}

sayHello();
//=> Hello Jack

Control Flow and hoisting

function sayHello() {
    logToConsole();

    var logToConsole = function() {
        var name = 'Jack';
        console.log('Hello ' + name);
    }

    logToConsole();
}

sayHello();
//=>TypeError: undefined is not a function

function sayHello() {
    var logToConsole;

    logToConsole();

    logToConsole = function() {
        console.log('Hello ' + name);
    }

    logToConsole();
}

Control Flow and hoisting


function sayHello() {
    function logToConsole() {
        var name = 'Jack';
        console.log('Hello ' + name);
    }

    logToConsole();
}

sayHello();
//=> Hello Jack

Control Flow and hoisting


function sayHello() {
    logToConsole();

    function logToConsole() {
        var name = 'Jack';
        console.log('Hello ' + name);
    }

    logToConsole();
}

sayHello();
//=> Hello Jack
//=> Hello Jack

function sayHello() {
    function logToConsole() {
        var name = 'Jack';
        console.log('Hello ' + name);
    }
    logToConsole();
    logToConsole();
}

Function-level scopes

function countTo(end) {    
    var numbers = [];
    for(var i = 1; i <= end; i++) {
        numbers.push(i);
    }
    console.log(numbers.join(','));
    var i = 11;
    console.log(i);
}
countTo(10);

Javascript does not have block-level scoping; it has function-level scoping.

//=>1,2,3,4,5,6,7,8,9,10
//=>11

function countTo(end) {    
    var numbers;
    var i;
    numbers = [];
    
    for(i = 1; i <= end; i++) {
        numbers.push(i);
    }
    console.log(numbers.join(','));
    i = 11;
    console.log(i);
}

Closures

function add(x) {
    return function(y) {
        return x + y;
    }
}

var add1 = add(1);

add1(2);
//=>3

Bonus: add1 is a partial application of add.

Global Scope

var name = 'Jack';

function sayHello() {
    console.log('Hello ' + name);
}

sayHello();

name and sayHello are now attached to the global object.

Global Scope

(function() {    
    var name = 'Jack';

    function sayHello() {
        console.log('Hello ' + name);
    }
    
    sayHello();
})();
sayHello();

Immediately-Invoked Function Expression (IIFE)

//=>Hello Jack
//
//sayHello();
//^
//ReferenceError: sayHello is not defined

(function() { /* code */ }());
!function() { /* code */ }();
~function() { /* code */ }();
+function() { /* code */ }();
-function() { /* code */ }();

Global Scope: Bonus

//We can use IIFEs and closures to emulate private properties and methods.
var car = (function() {
    var make = 'Honda';
    
    return {
        logMake: logMake
    };

    function logMake() {
      console.log(getMake());
    }
    
    function getMake() {
        return 'The car\'s make is a ' + make + '.';
    }
})();

car.logMake();
//=>The car's make is a Honda.

car.make = 'Tesla';
car.logMake();
//=>The car's make is a Honda.

console.log(car.make);
//=>Tesla

car.getMake();
//=>car.getMake();
//      ^
//TypeError: Object #<Object> has no method 'getMake'

Async

var promises = {};

function getCases() {
    var url = apiUrl + '/cases';
    var key = 'get:' + url;

    if (!promises[key]) {
        promises[key] = $http
            .get(url, httpConfig)
            .then(onSuccess, onError, onDone);
    }

    return promises[key];

    function onSuccess(success) {
        return success;
    }

    function onError(error) {
        errorStore.add(errorFactory.makeError('casesError', error.status));
    }

    function onDone() {
        delete promises[key];
    }
}

Javascript is executed in single thread and the can be unresponsive during long tasks. Return a promise and continue execution when the task is complete.

Async

//lineGraph.js
function lineGraph() {
    function init() {
        getCases().then(calculate).then(render);
    }
    
    function calculate(data) {
        return {
            xAxis: data.map(byAxis('x')),
            yAxis: data.map(byAxis('y')),
            lines: data.map(ByLines)            
        };
    }

    function render(data) {
        createLegend(data);
        createXAxis(data);
        createYAxis(data);
        displayLines(data);
    }

    init();
}

We can chain promises giving us a fluent interface

Async

//geoService.js
function factory($window, $q) {
    var api = {
        getGeoGps: getGeoGps
    };

    return api;

    function getGeoGps() {
        var options = {
            maximumAge: 60000, timeout: 500, enableHighAccuracy: true
        };

        var deferred = $q.defer();

        if (!$window.navigator || !$window.navigator.geolocation) {
            deferred.reject();
        } else {
            // get latitude and longitude from HTML5 Geolocation
            navigator.geolocation.getCurrentPosition(function onSuccess(position) {
                deferred.resolve(position);
            }, function onError(error) {
                deferred.reject(error);
            }, options);
        }

        return deferred.promise;
    }
}

Constructing a promise

A word of caution

this, prototype, call, apply, bind

MVW (Model View Whatever)

full-featured, opinionated framework

separation of concerns

designed to be testable

two-way binding

directives

Angular

Setup

-- root
 |- App_Start
    |- BundleConfig.cs
 |-Views
    |- Shared
       |- _Layout.cstml
 |- Scripts
    |- Lib
       |- angular.js
       |- angular-ui-router.js
    |- app
       |- widget
          |- configurations
             |- state.cs
          |- controllers
             |- controller.js
          |- directives
             |- directive.js
          |- services
             |- service.js
          |- views
             |- view.html
          |- app.js

Setup

// App_Start/BundleConfig.cs

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/Content/js/widget").Include(        
            "~/Scripts/lib/angular.js",
            "~/Scripts/lib/angular-ui-router.js",
            "~/Scripts/app/widget/app.js",
            "~/Scripts/app/widget/configurations/*.js",
            "~/Scripts/app/widget/controllers/*.js",
            "~/Scripts/app/widget/directives/*.js",
            "~/Scripts/app/widget/services/*.js"
        ));
    }
}

Bootstrapping

<!-- Views/_Layout.cshtml -->

<!DOCTYPE html>
    <head>
        <title>Widgets!</title>
        <link href="/~Content/app.css" rel="stylesheet">
    </head>
    <body>
        <div data-ng-app="widget">
            <div data-ui-view="content"></div>
        </div>
        @Scripts.Render("~/Content/js/widget");
    </body>
</html>

Modules

// Scripts/app/widget/app.js - module setter
!function(angular) {
    angular
        .module('widget', [
            'ui.router'
        ]);
}(angular);
// Scripts/app.widget/controllers/controller.js - module getter
!function(angular) {
    angular
        .module('widget')
        .controller('myController', controller);

    function controller() {
        /* code */
    }
}(angular);

Dependency Injection

// Scripts/app/widget/configurations/config.js

!function(angular) {
    angular
        .module('widget')
        .config(configuration);

    configuration.$inject = ['$stateProvider', 'baseUrl'];

    function configuration($stateProvider, baseUrl) {
        /* code */
    }
}(angular);

Routing

// Scripts/app/widget/configurations/config.js

!function(angular) {
    angular
        .module('widget')
        .config(configuration);

    configuration.$inject = ['$stateProvider', '$urlRouterProvider', 'baseUrl'];

    function configuration($stateProvider, $urlRouterProvider, baseUrl) {
        $stateProvider
            .state('index', {
                url: '/',
                views: {
                    'content': {
                        templateUrl: baseUrl + '/Scripts/app/widget/views/main.html',
                        controller: 'widget.mainController',
                        controllerAs: 'mainCtrl'
                    }
                }
            });

        $urlRouterProvider
            .otherwise('/');
    }
}(angular);

Controllers

// Scripts/app/widget/controllers/mainController.js

!function(angular) {
    angular
        .module('widget')
        .controller('widget.mainController', controller);

    controller.$inject = ['$scope', 'todoService'];

    function controller($scope, todoService) {
        var ctrl = this;

        var api = {
            data: {
                list: null,
            }      
        };

        angular.extend(ctrl, api);

        function init() {
            todoService.getList().then(function(success) {
                ctrl.data.list = success.data;
                $scope.$broadcast('todo list loaded');
            });
        }

        init();
    }
}(angular);

Two-way binding & Scopes

<!-- Scripts/app/widget/views/main.html -->
<ul data-ng-repeat="todo in mainCtrl.data.list | orderBy : 'dueOn'">
    <li>
        <input type="checkbox" data-ng-model="todo.isChecked">
        {{todo.name}} - {{todo.dueOn}} 
    </li>
</ul>
//watchers
$scope.$watch('property', function(newValue, oldValue) { /* code */ });
$scope.$watchGroup();
$scope.$watchCollection();

//Manually execute digest cycle for events outside of the framework
$scope.$apply();

//On destroy callback
$scope.$destroy(function() { /* code */ });

//event messaging
$scope.$emit('upstreamEvent', obj);
$scope.$broadcast('downstreamEvent', obj);
$scope.$on('upstreamEvent', function($event, obj) { /* code */ });

Directives

<!-- Scripts/app/widget/views/main.html -->
<ul data-ng-repeat="todo in mainCtrl.data.list | orderBy : 'dueOn'">
    <li data-todo-item="todo"></li>
</ul>
<!-- Scripts/app/widget/directives/todo.html -->
<input type="checkbox" data-ng-model="todo.isChecked" data-ng-click="check()">
{{todo.name}} - {{todo.dueOn}}
// Scripts/app/widget/directives/todo.js

!function (angular) {
    angular
        .module('widget')
        .directive('todoItem', directive);
    
    directive.$inject = ['todoService'];
    
    function directive(todoService) {
        var api = {
            restrict: 'EA',
            scope: {
                todo: '&todoItem'
            },
            templateUrl: '/Scripts/app/widget/directives/todo.html',
            link: link
        };

        return api;    

        function link(scope) {
            scope.check = function() {
                todoService.update(scope.todo.id, scope.todo);
            };
        }
    }
}(angular);

Directives

Services

!function (angular) {
    angular
        .module('widget')
        .factory('todoService', factory);

    factory.$inject = ['$http', 'baseUrl'];
    
    function factory($http, baseUrl) {
        var api = {
            getList: getList,
            get: get,
            update update,
            /* more methods */
        };

        return api;

        function getList() {            
            return $http.get(baseUrl + '/todo').then(onSuccess, onError);
        }
        
        function update(id, todo) {
            return $http.put(baseUrl + '/todo/' + id, todo).then(onSuccess, onError);
        }

        /* code */
    }
}(angular);

Into the Future

ECMAScript 6

&

Angular 2.0

React

&

Flux

Copy of Introduction into Angular

By Praveen Poonia

Copy of Introduction into Angular

Introduction into Angular with a brief discussion on unique aspects of javascript.

  • 901