Angular Tuts – An introduction to AngularJS – Fundamentals



Angular Tuts – An introduction to AngularJS – Fundamentals

0 1


angular_101


On Github kristofmic / angular_101

Angular 101

An introduction to AngularJS

By Chris Hourihan

Table of Contents

Angular Fundamentals Modules $scope Controllers Templating Providers (Services and Factories) Directives (core / custom) Filters (core / custom) Server Interactions ($http and $q) Testing (unit and e2e)

Fundamentals

What is Angular?

  • MVW or MV* framework
  • Designed to enhance HTML, making it dynamic
  • Removes DOM manipulation
  • Unopinionated approach
  • View it as an MVVM framework, where the VM is specifically targeted at linking the UI (View) to the Data/Logic (Model) via two-way data binding.
  • Data lives in a Model, our HTML lives as a tiny template to be rendered as a View, and we use a Controller to connect the two, driving Model and View value changes.
  • Angular keeps the DOM updated with any Model changes, and updates the underlying Model when a DOM input changes. The Model is simply a pure JavaScript Object for data-binding purposes.
  • Unlike Ember or Rails, Angular provides the tools to develop structured, rich client-side applications, but leaves the structure and organization up to the developer to decide. This can be a double-edged sword. Some people like the freedom to build things in a way they are comfortable with, however in larger teams it can become difficult to coordinate and it's easier to go down a wrong path that must be painfully changed later.
How does Angular work behind the scenes? HTML loads, Angular loads, Angular waits for the onContentReady event Angular inspects the DOM looking for an ng-app Angular loads the module, compiling all of the directives, controllers, services, etc Inspects the DOM and looks for what directives are on the page generates a template for each directive binds the template with the directive through a scope

Two-Way Data binding

  • Synchronizes the View with the Model and vice-versa via a $digest cycle and dirty checking
  • The underlying model is a plain-old JavaScript Object
  • The models are easily parsed to and from JSON, making server-side communication simple
  • Binding occurs in the templates via {{}} or ng-bind
  • Models can also be two way data-bound via the ng-model directive
  • Model becomes the single source of truth since it is always current
  • Any valid type in JavaScript can be bound to the ViewModel
  • Angular manages the data-binding via dirty checking and the $digest cycle, which checks everything in $scope for changes and then makes updates accordingly
$digest and $apply cycle
  • Angular achieves two-way data binding by waiting for stuff to happen (e.g., click, typing in a input), then go through and figure out what has changed on $scope (always watching what's on scope).
  • If values are different, then it kicks off an event watcher to trigger some functionality
  • Can manually kick of the $digest cycle via the $apply function (preferred over $digest because it includes some built in error handling)
  • Can also manually plug in a $watch function, to kick off some functionality when a value changes. These are usually created implicitly via directives.

Dependency Injection

  • Software design pattern for resolving dependencies
  • AngularJS provides the $injector service which does this automatically by inspecting the arguments of a function
  • To handle minification/uglification, Angular provides an alernate syntax via an Array and Strings
  • Lazy loaded
  • Dependencies are explicitly declared and passed to the object or function
  • The $injector typically does not need to be called explicitly, Angular does the resolving of dependencies for us
  • Angular will throw an error if it cannot resolve a dependency
  • Lazy loading works by first checking cache when a dependency is requested, if in cache, will return that. otherwise it will execute the dependency's factory function to instantiate a new one and give you that, adding the value to cache for future requests

Modules

Modules

  • Applications are built from Modules
  • Modules are the containers for a component of an application
  • Modules can depend on other modules
  • Modules make our code reusable, limit scope, and enable testability

Module Syntax

Setter

	angular.module('MyModuleName', [ModuleDependency1, md2, md3, ...]);
							

Getter

	angular.module('MyModuleName')
		.controller('MyController', function() {});
							
  • Modules can have dependencies on other modules, which in turn may have other dependencies of their own
  • The Module dependency declarations allow a service within your Module to use a service from another Module
  • Creating other services in Angular requires you to have an instance of the Module it will be attached to
  • Modules can be stared in JavaScript variables, however with the Getter syntax, this is unnecessary and should be discouraged

Loading the Module in HTML

	<html>
	  <head></head>
	  <body ng-app="MyModuleName">
	    <h1>Hello World</h1>
	  </body>
	</html>
						
  • The ng-app directive takes the Module name as an argument and loads that Module, giving the Module access to and control over the DOM element it was placed on and all of its children.

$scope

$scope

  • Represents the ViewModel
  • Anything attached to the $scope object is then accessible in the HTML
	$scope.heading = 'Hello World!';
							
	<body ng-app="MyModuleName" ng-app="MyController">
	  <h1>{{heading}}</h1> <!-- <h1>Hello World!</h1> -->
	</body>
							
$scope is only used in our Controllers and Directives, where we bind data to a view The $rootScope object is the parent of all $scopes
  • $scope is the link between your presentation layer (View) and the data/business logic (Model)
  • It enables the two-way data binding
  • The DOM outside of the scope of the Controller (or Directive) is not accessible via the $scope
  • Properties on $rootScope will be accessible via other $scopes through the prototype chain of inheritance (NOTE: be careful about what you inherit. It's easy to shadow parent properties with child properties that are no longer linked).

Controllers

Controllers

  • Controllers are responsible for setting up the link between your View and Model (i.e., $scope)
	angular.module('MyModuleName')
		.controller('MyController', function($scope, dependency) {
	                 $scope.heading = 'Hello World!';
		});
							
	// Syntax for minification/uglification
	angular.module('MyModuleName')
		.controller('MyController', ['$scope', 'dependency', function($scope, dependency) {
			$scope.heading = 'Hello World!';
		});
							
  • In the template, the ng-controller directive tells Angular where to bind an instance of a Controller and make the Controller's data and methods available in that DOM scope.
  • Controller function accepts the Controller's name as a String, and a Function that represents the Controller's body
  • To ensure the DI works correctly in minified code, an alternate syntax of passing an Array is used, where the inital values of the Array are the String representation of the Controller's dependencies, and the last argument is the Controller's body (i.e., the Function callback).
  • Controllers should not own the domain model, they should be responsible for setting up the ViewModel and linking functionality of the ViewModel to the appropriate services

Templating

Expressions

  • "JavaScript-like" pieces of code that conditionally change the View (DOM) to match the Model
  • Live in {{}} or ng-bind and evaluated against $scope
  • Angular re-evaluates these expressions during each $digest cycle, making updates based on any changes
  • Can contain limited bits of JavaScript logic including basic operators (e.g., &&, ||, ?:, >/<, +/-, ===, etc.)

Example

	$scope.heading = 'Hello World!';
	$scope.updateHeading = function(newHeading) {
		$scope.heading = newHeading;
	}
						
	<h1>{{heading}}</h1> <!-- <h1>Hello World!</h1>-->
	<button ng-click="updateHeading('Foobar')">Update</button> <!-- Click -->
						
	<h1>{{heading}}</h1> <!-- <h1>Foobar</h1>-->
						

Providers

Services and Factories

  • Containers for our application's Model, where the data and business logic should live
  • Services are invoked with new, thus all data/logic should be bound to this
  • Factories are not invoked with new and can return anything (e.g., Functions, Objects, Arrays, simple values)
  • Everything returned from either a Service or Factory is a singleton
  • Take advantage of closures and can create privately scope variables
  • When naming Factories, recommend still use the word 'service' unless they are truly a Factory for creating other objects
  • The fact that they are singletons is very important and powerful. This means that regardless of where a Service or Factory object is used, it's always the same object. This enables us to share data across Controllers and maintain state with our application.

Service Syntax

angular.module('MyModule')
	.service('MyService', [myService]);

function myService() {
	this.bar = 'bar';
	this.doSomething = function doSomething() {
		return 'foo' + this.bar;
	}
}
						
angular.module('MyModule')
	.controller('MyController', ['$scope', 'MyService', myController]);

function myController($scope, myService) {
	$scope.heading = myService.doSomething();
}
						
<h1>{{heading}}</h1> <!-- <h1>foobar</h1>-->
						
  • Service objects are just constructors that only get invoked once

Factory Syntax

angular.module('MyModule')
	.factory('MyService', [myService]);

function myService() {
	var bar = 'bar';

	return {
			doSomething: doSomething
	}

	function doSomething() {
		return 'foo' + bar;
	}
}
						
angular.module('MyModule')
	.controller('MyController', ['$scope', 'MyService', myController]);

function myController($scope, myService) {
	$scope.heading = myService.doSomething();
}
						

Providers

  • Used throughout your application just like Factories or Services, however they can also be passed into the .config() of your Module
  • The config of a module is executed before the DI invokes the Service or Factory functions, enabling you to setup certain parts of your service/factory
  • Other Providers include Values and Constants, to set simple values. Constants are accessible at during the config phase of a module.

Provider Syntax

angular.module('MyModule')
    .provider('MyService', function() {
        var bar = 'bar';

    return {
        setBar: function(newBar) { bar = newBar; },
        $get: function() {
            return {
                doSomething: function() { return 'foo' + bar; }
            };
        }
    };
  });
						
angular.module('MyModule')
    .config(function(MyServiceProvider) {
        MyServiceProvider.setBar('baz');
    })
    .controller('MyController', function($scope, MyService) {
        $scope.heading = MyService.doSomething();
    });
						

Directives

What are Directives?

  • Powerhouses of AngularJS and are the underlying components that make a rich client-side application
  • Can enhance existing elements with functionality (e.g., ng-required, ng-click)
  • Can extend functionality to previously non-functional elements (e.g., ng-repeat, ng-class, ng-show/hide)
  • Can be brand-new elements with their own functionality (e.g., ng-include)
  • Directives can be set via custom element tags, attributes, classes, or uncommonly as comments
  • Anything you previously did with jQuery where you reached into the DOM (e.g., selectors) can be done via Angular Directives

Common Examples

  • ng-repeat provides a repeater for displaying the same template over a collection of like elements
  • ng-model allows us to bind HTML inputs to Models on our $scope
  • ng-click allows us to handle click events on buttons, anchors or any other element, with the function bound only to the local $scope
  • ng-href/ng-src allow us to dynamically set href and src attributes. Important since we want angular to resolve the href/src before it's clicked or the image downloaded
  • ng-class allows us to dynamically add/remove classes to elements

Common Examples Cont.

  • ng-show/ng-hide allow us to toggle the display property of elements based on a boolean (or truthy/falsy) value
  • ng-if will actually remove the element from the DOM and the $scope it creates
  • ng-switch removes elements from the DOM based on a value (doesn't have to be boolean) and can apply to multiple elements like a switch statement
  • ng-bind is an alternative to {{}} that prevents edge-cases where there is a brief flicker of the {{}} before Angular resolves the value
  • ng-include allows you to pull in a template of HTML from the server or pre-loaded in the template cache

Custom Directives - Usage

  • Enable us to create and encapsulate new functionality and views in reusable and injectable way
  • Can be used as tags, attributes, classes, or comments as specified via the restrict property on the Directive Definition Object (DDO)
  • E for Element/Tag, A for Attribute, C for Class, M for Comment, Default to EA
  • Follow a kebab-case convention for use within the DOM (in JavaScript, use camelCase, Angular handles the conversion)
<my-directive></my-directive>
<div my-directive></div>
<div class="my-directive"></div>
<!-- directive: my-directive -->
						
  • Directives are akin to Web Components or React Components
  • Attributes are preferred which prevent older IE from complaining and are generally more clear than classes/comments

Custom Directives - Templates

  • Directives can be used as templating engines, so you not only extend an element with functionality, but also extend the DOM with content
  • template and templateUrl are the two ways of associating DOM content with a Directive
  • template is just a string representing HTML
  • templateUrl is a reference to a public HTML asset on your server, a pre-existing asset in the $templateCache, or a template defined as a custom script tag using the custom type of "text/ng-template"

Custom Directives - DDO

  • The Directive Definition Object is the API for creating Directives
  • Simple JavaScript Object with certain properties
function myDirective() {
    return {
        restrict: 'EA', // how the directive can be used
        templateUrl: 'myDirective.html', // template HTML
        scope: true, // how we setup the directive's scope
                     // true => new scope prototypally inherited from parent
                     // false => parent scope
                     // object literal => isolate scope where custom properties can be injected
        link: function link(scope, elem, attrs) {}, // provides DOM access
        controller: function myController($scope) {}, // like other controllers, except specific to the directive's scope

    }
}
							
  • The link function is executed after the Directive is compiled and the template (if provided) is inserted into the DOM, allowing us to bind event listeners or perform DOM manipulation based on scope properties or other logic
  • It's important to approach DI with Controllers just as with other Controllers, so to handle minification/uglification you must use Array and String notation. This does not apply to Link functions.
  • Other less common DDO properties are listed here

Filters

What are Filters?

  • Provide a means of processing data and returning a transformed version of it (e.g., filtered list, specific date format)
  • Filters can be used in templates via the | character
{{ object_expression | filterName : expression : comparator}}
							
Filters can be used in JavaScript via the $filter service
$filter('filterName')(object, expression, comparator)
							
Filters do not alter the underlying data
  • single value or collection based
  • object/object_expression is the JavaScript Object to be passed through the filter function
  • filterName is the filter to be used (e.g., 'filter', 'date', 'uppercase')
  • expression is the predicate to be used for selecting certain elements from a collection (String, Object, or Function callback...via a function callback, custom filters can be created outside of the .filter function on modules)
  • comparator is means of telling Angular whether the actual and expected items are considered 'equal'

Common Examples

  • date converts a date to a specified format and timezone
  • limitTo limits the elements returned in the collection to the specified number
  • orderBy orders the elements returned in the collection based on the predicate
  • linky finds links in a snippet of text and converts them to actual Anchor links
  • orderBy defaults to descending order, but can be reversed with a third boolean parameter

Custom Filters - Usage

  • Use the filter function and return a function used to filter the object
  • Angular will pass the value or collection to be run through the filter function
angular.module('MyModule')
    .filter('capitalize', function() {
        return function capitalizer(text) {
            return text.charAt(0).toUpperCase() + text.slice(1);
        };
    })
    .controller('MyController', function($scope) {
        $scope.heading = 'foobar';
    });
							
<h1>{{heading | capitalize}}</h1> <!-- <h1>Foobar</h1>-->
						
  • Custom Filters benefit from two-way data binding and are executed on each $digest cycle automatically
  • Custom filters for collections are generally the same and single value filters: the collection will be passed to your function and you must return the subset of objects that meet your filtering conditions
  • Additional arguments can be passed into your filter function by colon (:) delimiting the arguments in the expression (e.g., capitlize:onlyStringsStartingWithA => | capitalize:'a'
  • Note that because the generalized 'filter' filter can take a function callback as a predicate, custom filters can be created without using the customer .filter module function

Server Interactions

$http

  • $http is a service that enables server-side communication via AJAX requests
  • $http is both a function, whereby an AJAX configuration object is passed in, and an object with convenience GET, POST, DELETE, and PUT methods (also exposes HEAD, JSONP, and PATCH)
  • Returns a Promise object based on the $q service, with additional success and error convenience functions
  • Note the Success and Error functions returned from an $http request are convenience wrappers for Then and Catch promise functions. Success and Error functions are automatically passed in the data, status, headers and config params, where .Then/.Catch are only passed the raw response containing the aforementioned properties.
  • $http responses are wrapped in and $apply call, so if we update $scope bindings based on a response, they will be automatically updated in the View
  • You can bind the $http Promise response to a $scope property and Angular will automatically resolve the Promise value when it is fulfilled

$q

  • $q is a simplified Promise library implementation that supports then, catch, finally, and all methods
  • Exposes a deferred API
function asyncGreet(name) {
    var deferred = $q.defer();
    setTimeout(function() {
        if (okToGreet(name)) { deferred.resolve('Hello, ' + name + '!'); }
        else { deferred.reject('Greeting ' + name + ' is not allowed.'); }
    }, 1000);
    return deferred.promise;
}
							
  • Note the Success and Error functions returned from an $http request are convenience wrappers for Then and Catch promise functions. Success and Error functions are automatically passed in the data, status, headers and config params, where .Then/.Catch are only passed the raw response containing the aforementioned properties.
  • $http responses are wrapped in and $apply call, so if we update $scope bindings based on a response, they will be automatically updated in the View
  • You can bind the $http Promise response to a $scope property and Angular will automatically resolve the Promise value when it is fulfilled

Testing

Tech Overview

  • Karma - Unit Test Runner
  • Protractor - E2E Test Runner
  • Jasmine - Test Framework and Assertion Library
  • Mocha, Chai, and Sinon - Test Framework and Assertion Library
  • ngMock - AngularJS Module to Mock and Inject Services

Unit Testing - Setting Up Tests

Load the module of the component being tested
describe('MyController', function() {
	beforeEach(module('ch.Main'));
});
							
Set stubbed out or mocked out services
beforeEach(module(function($provide) {
  stubbedService = {
  	mockMethod: jasmine.createSpy('mockedMethod')
  };

  $provide.value('MyService', stubbedService);
}));
							

Unit Testing - Setting Up Tests Cont.

Inject any other needed services and load your component
beforeEach(inject(function($rootScope, $injector, $controller) {
  anotherService = $injector.get('anotherService');
  scope = $rootScope.$new();

  myController = $controller('MyController', {
  	$scope: scope,
  	anotherService: anotherService
  })
}));
							
Write your tests!
it('should exist', function() {
  expect(myController).toBeDefined();
});
								

E2E Testing

  • Protractor is built on top of WebDriverJS, which wraps a Selenium server for doing browser-based automation testing
  • Code-based API for interacting with elements on the page
  • Works with both Jasmine and Mocha/Chai/Sinon testing frameworks
it('should default to the correct page', function() {
  expect(browser.getCurrentUrl()).toContain('/home');
});
						
it('should set the correct heading', function() {
  expect(element(by.css('.heading')).getText()).toEqual('Hello World!');
});
						

Additional Resources and References