Angular JS

  • Posted on: 3 March 2017
  • By: vinodkumartiwari

What Is Angular?

A structural framework for dynamic web applications. Angular's data binding and dependency injection eliminate much of the code. Data bind as in {{}}. The DOM control structures for repeating, showing and hiding DOM fragments. Supports for forms and form validation. Attaching new behaviour to DOM elements, such as DOM event handling. Grouping of HTML into reusable components.

Conceptual Overview

Template - HTML with additional markup Directives - extended HTML with custom attributes and elements Model - the data shown to the user in the view and with which the user interacts Scope - context where the model is stored so that controllers, directives and expressions can access it Expressions - access variables and functions from the scope Compiler - parses the template and instantiates directives and expressions Filter - formats the value of an expression for display to the user View - what the user sees (the DOM) Data Binding - sync data between the model and the view Controller - the business logic behind views Dependency Injection - Creates and wires objects and functions Injector - dependency injection container Module - a container for the different parts of an app including controllers, services, filters, directives which configures the Injector Service - reusable business logic independent of views.

AngularJS Controllers

A Controller is defined by a JavaScript constructor function. A new child scope will be created and made available as an injectable parameter ($scope). Used to set up the initial state of the $scope object. Used to add behaviour to the $scope object.

Scope Inheritance


var vApp = angular.module('scopeInheritance', []);'
vApp.controller('MainController', ['$scope', function($scope) {
$scope.timeOfDay = 'morning';
$scope.name = 'Vinod';
}]);

myApp.controller('ChildController', ['$scope', function($scope) {
$scope.name = 'Anita';
}]);

vApp.controller('GrandChildController', ['$scope', function($scope) {
$scope.timeOfDay = 'morning';
$scope.name = 'Baby';
}]);

Controller Test


describe('vController function', function() {
describe('vController', function() {
var $scope;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('vController', {$scope: $scope});
}));
it('should create "person" model with 3 spices', function() {
expect($scope.person.length).toBe(3);
});
it('should set the default value of person', function() {
expect($scope.person).toBe('VINOD');
});
});
});

If we need to test a nested Controller it must create the same scope hierarchy in your test that exists in the DOM:


describe('state', function() {
var mainScope, childScope, grandChildScope;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
mainScope = $rootScope.$new();
$controller('MainController', {$scope: mainScope});
childScope = mainScope.$new();
$controller('ChildController', {$scope: childScope});
grandChildScope = childScope.$new();
$controller('GrandChildController', {$scope: grandChildScope});
}));
it('should have over and selected', function() {
expect(mainScope.timeOfDay).toBe('morning');
expect(mainScope.name).toBe('Nikki');
expect(childScope.timeOfDay).toBe('morning');
expect(childScope.name).toBe('Mattie');
expect(grandChildScope.timeOfDay).toBe('evening');
expect(grandChildScope.name).toBe('Gingerbread Baby');
});
});

Services

Angular services are substitutable objects that are wired together using dependency injection (DI).
Services can be used to organise and share code across your application.

AngularJS Services

Lazily instantiated – Angular only instantiates a service when an application component depends on it.
Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.
To use the service, need to add into the dependency for the component (controller, service, filter or directive)

Creating a Service

Application developers are free to define their own services by registering the service's name and service factory function, with an Angular module.
The service factory function generates the single object or function that represents the service to the rest of the application.
The object or function returned by the service is injected into any component (controller, service, filter or directive) that specifies a dependency on the service.

Registering Services

Services are registered to modules via the Module API.
Typically you use the Module factory API to register a service.

Registering a Service with $provide

We can also register services via the $provide service inside of a module's config function.
This technique is often used in unit tests to mock out a service's dependencies.

Scopes

Scope is an object that refers to the application model.
Scopes provide APIs ($watch) to observe model mutations.
Scopes provide APIs ($apply) to propagate any model changes through the system into the view.
Scopes can be nested to limit access to the properties of application components. Nested scopes are either "child scopes" or "isolate scopes".

Scope as Data-Model

Scope is the glue between application controller and the view.
During the template linking phase the directives set up $watch expressions on the scope.
The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.
Scope Hierarchies

Each Angular application has exactly one root scope, but may have several child scopes.
The application can have multiple scopes, because some directives create new child scopes.
When new scopes are created, they are added as children of their parent scope.
This creates a tree structure which parallels the DOM where they're attached.
Retrieving Scopes from the DOM

Scopes are attached to the DOM as $scope data property, and can be retrieved for debugging purposes.
The location where the root scope is attached to the DOM is defined by the location of ng-app directive.
To examine the scope in the debugger:
Right click on the element of interest in your browser and select 'inspect element'. You should see the browser debugger with the element you clicked on highlighted.
The debugger allows you to access the currently selected element in the console as $0 variable.
To retrieve the associated scope in console execute: angular.element($0).scope() or just type $scope.
Scope Events Propagation

Scopes can propagate events in similar fashion to DOM events.
The event can be broadcasted to the scope children or emitted to scope parents.

AngularJS provides $on, $emit, and $broadcast services for event-based communication between controllers.

$emit (Bubbling) - It dispatches an event name upwards through the scope hierarchy and notify to the registered $rootScope.
$broadcast (Capturing) - It dispatches an event name downwards to all child scopes (and their children) and notify to the registered $rootScope.Scope listeners.

$on - It listen on events of a given type. It can catch the event dispatched by $broadcast and $emit.

Scope Life Cycle

The normal flow of a browser receiving an event is that it executes a corresponding JavaScript callback.
Once the callback completes the browser re-renders the DOM and returns to waiting for more events.
When the browser calls into JavaScript the code executes outside the Angular execution context, which means that Angular is unaware of model modifications. To properly process model modifications the execution has to enter the Angular execution context using the $apply method.
Only model modifications which execute inside the $apply method will be properly accounted for by Angular.
After evaluating the expression, the $apply method performs a $digest.
In the $digest phase the scope examines all of the $watch expressions and compares them with the previous value.
This dirty checking is done asynchronously.
If a $watch changes the value of the model, it will force additional $digest cycle.

Scope Life Cycle

  1. Creation
  2. Watcher Registration
  3. Model Mutation
  4. Mutation observation
  5. Scope destruction

Creation - The root scope is created during the application bootstrap by the $injector. During template linking, some directives create new child scopes.
Watcher registration - During template linking directives register watches on the scope. These watches will be used to propagate model values to the DOM.
Model mutation - For mutations to be properly observed, you should make them only within the scope.$apply(). Angular APIs do this implicitly, so no extra $apply call is needed when doing synchronous work in controllers, or asynchronous work with $http, $timeout or $interval services.
Mutation observation - At the end of $apply, Angular performs a $digest cycle on the root scope, which then propagates throughout all child scopes. During the $digest cycle, all $watched expressions or functions are checked for model mutation and if a mutation is detected, the $watch listener is called.
Scope destruction - When child scopes are no longer needed, it is the responsibility of the child scope creator to destroy them via scope.$destroy() API. This will stop propagation of $digest calls into the child scope and allow for memory used by the child scope models to be reclaimed by the garbage collector.

Scopes and Directives

During the compilation phase, the compiler matches directives against the DOM template. The directives usually fall into one of two categories:

Observing directives, such as double-curly expressions {{expression}}, register listeners using the $watch() method. This type of directive needs to be notified whenever the expression changes so that it can update the view.

Listener directives, such as ng-click, register a listener with the DOM. When the DOM listener fires, the directive executes the associated expression and updates the view using the $apply() method.

When an external event (such as a user action, timer or XHR) is received, the associated expression must be applied to the scope through the $apply() method so that all listeners are updated correctly.

Directives that Create Scopes

In most cases, directives and scopes interact but do not create new instances of scope. However, some directives, such as ng-controller and ng-repeat, create new child scopes and attach the child scope to the corresponding DOM element.
We can retrieve a scope for any DOM element by using an angular.element(aDomElement).scope() method call.

Controllers and Scopes

Controllers use scopes to expose controller methods to templates.
Controllers define methods (behavior) that can mutate the model.
Controllers may register watches on the model. These watches execute immediately after the controller behavior executes.
Scope $watch Performance Considerations

Dirty checking the scope for property changes is a common operation in Angular and for this reason the dirty checking function must be efficient.

Scope $watch Depths

  • Watching by reference (scope.$watch (watchExpression, listener))
  • Watching collection contents (scope.$watchCollection (watchExpression, listener))
  • Watching by value (scope.$watch (watchExpression, listener, true))

$apply(), $digest(), and $watch

Data binding means that when we change something in the view, the scope model automatically updates. Similarly, whenever the scope model changes, the view updates itself with the new value.

When we write an expression ({{aModel}}), behind the scenes Angular sets up a watcher on the scope model, which in turn updates the view whenever the model changes.
This watcher is just like any watcher you set up in AngularJS:


$scope.$watch('aModel', function(newValue, oldValue) {
//update the DOM with newValue
});

The second argument passed to $watch() is known as a listener function, and is called whenever the value of aModel changes.
How does AngularJS know when aModel changes? It’s the $digest cycle where the watchers are fired.
When a watcher is fired, AngularJS evaluates the scope model, and if it has changed then the corresponding listener function is called.
The $digest cycle starts as a result of a call to $scope.$digest().
Assume that you change a scope model in a handler function through the ng-click directive. In that case AngularJS automatically triggers a $digest cycle by calling $digest(). When the $digest cycle starts, it fires each of the watchers. These watchers check if the current value of the scope model is different from last calculated value. If yes, then the corresponding listener function executes. As a result if you have any expressions in the view they will be updated. In addition to ng-click, there are several other built-in directives/services that let you change models (e.g. ng-model, $timeout, etc) and automatically trigger a $digest cycle.
Angular doesn’t directly call $digest(). Instead, it calls $scope.$apply(), which in turn calls $rootScope.$digest(). As a result of this, a digest cycle starts at the $rootScope, and subsequently visits all the child scopes calling the watchers along the way.

Note: $scope.$apply() automatically calls $rootScope.$digest(). The $apply() function comes in two flavours.

The first one takes a function as an argument, evaluates it, and triggers a $digest cycle.
The second version does not take any arguments and just starts a $digest cycle when called.

Call apply() Manually

Angular’s built-in directives already do this so that any model changes you make are reflected in the view. However, if you change any model outside of the Angular context, then you need to inform Angular of the changes by calling $apply() manually. It’s like telling Angular that you are changing some models and it should fire the watchers so that your changes propagate properly.

Dependency Injection

Dependency Injection (DI) is a software design pattern that deals with how components get hold of their dependencies.
The Angular injector subsystem is in charge of creating components, resolving their dependencies, and providing them to other components as requested.

Using Dependency Injection

DI is pervasive throughout Angular. You can use it when defining components or when providing run and config blocks for a module.

Components such as services, directives, filters, and animations are defined by an injectable factory method or constructor function. These components can be injected with "service" and "value" components as dependencies.
Controllers are defined by a constructor function, which can be injected with any of the "service" and "value" components as dependencies, but they can also be provided with special dependencies.
The run method accepts a function, which can be injected with "service", "value" and "constant" components as dependencies. Note that we cannot inject "providers" into run blocks.
The config method accepts a function, which can be injected with "provider" and "constant" components as dependencies. Note that we cannot inject "service" or "value" components into configuration.

Templates

These are the types of Angular elements and attributes we can use:

  • Directive — An attribute or element that augments an existing DOM element or represents a reusable DOM component.
  • Markup — The double curly brace notation {{ }} to bind expressions to elements is built-in Angular markup.
  • Filter — Formats data for display.
  • Form controls — Validates user input.

Expressions

Angular does not use JavaScript's eval() to evaluate expressions. Instead Angular's $parse service processes these expressions.
Angular expressions do not have access to global variables like window, document or location.
Directives like ngClick and ngFocus expose a $event object within the scope of that expression. The object is an instance of a jQuery Event Object when jQuery is present or a similar jqLite object.

One-time Binding

An expression that starts with :: is considered a one-time expression.
One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value.
Reasons for using one-time binding - åThe main purpose of one-time binding expression is to provide a way to create a binding that gets deregistered and frees up resources once the binding is stabilized. Reducing the number of expressions being watched makes the digest loop faster and allows more information to be displayed at the same time.

Value Stabilization Algorithm

One-time binding expressions will retain the value of the expression at the end of the digest cycle as long as that value is not undefined. If the value of the expression is set within the digest loop and later, within the same digest loop, it is set to undefined, then the expression is not fulfilled and will remain watched.

Given an expression that starts with ::, when a digest loop is entered and expression is dirty-checked, store the value as V
If V is not undefined, mark the result of the expression as stable and schedule a task to deregister the watch for this expression when we exit the digest loop
Process the digest loop as normal.

When digest loop is done and all the values have settled, process the queue of watch deregistration tasks. For each watch to be deregistered, check if it still evaluates to a value that is not undefined. If that's the case, deregister the watch. Otherwise, keep dirty-checking the watch in the future digest loops by following the same algorithm starting from step 1
Interpolation and data-binding.

Interpolation markup with embedded expressions is used by Angular to provide data-binding to text nodes and attribute values.

Hello {{username}}!
During the compilation process the compiler uses the $interpolate service to see if text nodes and element attributes contain interpolation markup with embedded expressions.

ngAttr for binding to arbitrary attributes


<svg>
<circle cx="{{cx}}"></circle>
</svg>

Error: Invalid value for attribute cx="{{cx}}"


>svg<
<circle ng-attr-cx="{{cx}}"></circle>
>/svg<

Why mixing interpolation and expressions is bad practice:

It increases the complexity of the markup
There is no guarantee that it works for every directive, because interpolation itself is a directive. If another directive accesses attribute data before interpolation has run, it will get the raw interpolation markup and not data.
It impacts performance, as interpolation adds another watcher to the scope.
Since this is not recommended usage, we do not test for this, and changes to Angular core may break your code.

Filters

A filter formats the value of an expression for display to the user. They can be used in view templates, controllers or services and it is easy to define your own filter.

Creating custom filters

Just register a new filter factory function with your module. Internally, this uses the filterProvider.

Stateful Filters

It is strongly discouraged to write filters that are stateful, because the execution of those can't be optimized by Angular, which often leads to performance issues.
Many stateful filters can be converted into stateless filters just by exposing the hidden state as a model and turning it into an argument for the filter.
If you however do need to write a stateful filter, you have to mark the filter as $stateful, which means that it will be executed one or more times during the each $digest cycle.


angular.module('myStatefulFilterApp', [])
.filter('decorate', ['decoration', function(decoration) {
function decorateFilter(input) {
return decoration.symbol + input + decoration.symbol;
}
decorateFilter.$stateful = true;
return decorateFilter;
}])
.controller('MyController', ['$scope', 'decoration', function($scope, decoration) {
$scope.greeting = 'hello';
$scope.decoration = decoration;
}])
.value('decoration', {symbol: '*'});

Form

Form and controls provide validation services.
The user can be notified of invalid input before submitting a form.
Server-side validation is still necessary for a secure application.
novalidate is used to disable browser's native form validation.

Using CSS Classes

  • ng-valid: the model is valid
  • ng-invalid: the model is invalid
  • ng-valid-[key]: for each valid key added by $setValidity
  • ng-invalid-[key]: for each invalid key added by $setValidity
  • ng-pristine: the control hasn't been interacted with yet
  • ng-dirty: the control has been interacted with
  • ng-touched: the control has been blurred
  • ng-untouched: the control hasn't been blurred
  • ng-pending: any $asyncValidators are unfulfilled

Binding to form and control state

A form is an instance of FormController.
The form instance can optionally be published into the scope using the name attribute.

What are the Directives?

At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class).
Tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform the DOM element and its children.
When Angular bootstraps the app, the HTML compiler traverses the DOM matching directives against the DOM elements.

Angular Compilation

For AngularJS, "compilation" means attaching directives to the HTML to make it interactive.
The reason we use the term "compile" is that the recursive process of attaching directives mirrors the process of compiling source code in compiled programming languages.

Normalization

Angular normalizes an element's tag and attribute name to determine which elements match which directives.
We typically refer to directives by their case-sensitive camelCase normalized name (e.g. ngModel).
However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case forms, typically using dash-delimited attributes on DOM elements (e.g. ng-model).
The normalization process is as follows:

Strip x- and data- from the front of the element/attributes.
Convert the :, -, or _-delimited name to camelCase.

Directive Types

$compile can match directives based on element names, attributes, class names, as well as comments.



Best Practice: 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.

Creating Directives

To register a directive, we use the module.directive API. It takes the normalized directive name followed by a factory function. This factory function should return an object with the different options to tell $compile how the directive should behave when matched. The factory function is invoked only once when the compiler matches the directive for the first time. You can perform any initialization work here. The function is invoked using $injector.invoke which makes it injectable just like a controller. The restrict option is typically set to:

  • 'A' - only matches attribute name
  • 'E' - only matches element name
  • 'C' - only matches class name
  • 'M' - only matches comment

Isolating the Scope of a Directive

What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an isolate scope.

script.js

angular.module('docsIsolateScopeDirective', []) .controller('Controller', ['$scope', function($scope) { $scope.naomi = { name: 'Naomi', address: '1600 Amphitheatre' }; $scope.igor = { name: 'Igor', address: '123 Somewhere' }; }]) .directive('myCustomer', function() { return { restrict: 'E', scope: { customerInfo: '=info' }, templateUrl: 'my-customer-iso.html' }; });

index.html


Creating a Directive that Manipulates the DOM

Directives that want to modify the DOM typically use the link option to register DOM listeners as well as update the DOM. It is executed after the template has been cloned and is where directive logic will be put. link takes a function with the following signature

function link(scope, element, attrs, controller, transcludeFn) { ... },

where:

Scope is an Angular scope object. element is the jqLite-wrapped element that this directive matches. attrs is a hash object with key-value pairs of normalized attribute names and their corresponding attribute values. controller is the directive's required controller instance(s) or its own controller. transcludeFn is a transclude linking function pre-bound to the correct transclusion scope. There are a few special events that AngularJS emits. When a DOM node that has been compiled with Angular's compiler is destroyed, it emits a $destroy event. Similarly, when an AngularJS scope is destroyed, it broadcasts a $destroy event to listening scopes.

Creating a Directive that Wraps Other Elements

Transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside. The transclude option changes the way scopes are nested. It makes it so that the contents of a transcluded directive have whatever scope is outside the directive, rather than whatever scope is on the inside. In doing so, it gives the contents access to the outside scope. This behavior makes sense for a directive that wraps some content, because otherwise you'd have to pass in each model you wanted to use separately.

Best Practice: only use transclude: true when you want to create a directive that wraps arbitrary content.

Creating a Directive that Adds Event Listeners

element.on('mousedown', function(event) { // Prevent default dragging of selected content event.preventDefault(); startX = event.pageX - x; startY = event.pageY - y; $document.on('mousemove', mousemove); $document.on('mouseup', mouseup); });

Creating Directives that Communicate

You can compose any directives by using them within templates. Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.

Understanding Components

A Component is a special kind of directive that uses a simpler configuration which is suitable for a component-based application structure.

Advantages of Components:

simpler configuration than plain directives promote sane defaults and best practices optimized for component-based architecture writing component directives will make it easier to upgrade to Angular 2

When not to use Components:

For directives that rely on DOM manipulation, adding event listeners etc, because the compile and link functions are unavailable when you need advanced directive definition options like priority, terminal, multi-element when you want a directive that is triggered by an attribute or CSS class, rather than an element.

Component-based application architecture

Components only control their own View and Data. Components have a well-defined public API - Inputs and Outputs. Components have a well-defined lifecycle. An application is a tree of components.

Component Router

  • Router - Displays the Routing Components for the active Route. Manages navigation from one component to the next.
  • RootRouter - The top level Router that interacts with the current URL location.
  • RouteConfig - Configures a Router with RouteDefinitions, each mapping a URL path to a component.
  • Routing Component - An Angular component with a RouteConfig and an associated Router.
  • RouteDefinition - Defines how the router should navigate to a component based on a URL pattern.
  • ngOutlet - The directive () that marks where the router should display a view.
  • ngLink - The directive (ng-link="...") for binding a clickable HTML element to a route, via a Link Parameters Array.
  • Link Parameters Array - An array that the router interprets into a routing instruction. We can bind a RouterLink to that array or pass the array as an argument to the Router.navigate method.

Component-based Applications

It is recommended to develop AngularJS applications as a hierarchy of Components. Each Component is an isolated part of the application, which is responsible for its own user interface and has a well defined programmatic interface to the Component that contains it.

URLs and Navigation

Users navigate from one view to the next as they perform application tasks. The browser provides a familiar model of application navigation. We enter a URL in the address bar or click on a link and the browser navigates to a new page. We click the browser's back and forward buttons and the browser navigates backward and forward through the history of pages we've seen.

Component Routes

When using the Component Router, each Component in the application can have a Router associated with it. This Router contains a mapping of URL segments to child Components.

$routeConfig: [ { path: '/a/b/c', component: 'someComponent' }, ... ]

Outlets

Each Routing Component, needs to have a template that contains one or more Outlets, which is where its child Components are rendered. We specify the Outlet in the template using the directive.

Root Router and Component

How does the Component Router know which Component to render first? All Component Router applications must contain a top level Routing Component, which is associated with a top level Root Router. The Root Router is the starting point for all navigation. You can access this Router by injecting the $rootRouter service. We define the top level Root Component by providing a value for the $routerRootComponent service. Route Matching

When we navigate to any given URL, the $rootRouter matches its Route Config against the URL. If a Route Definition in the Route Config recognises a part of the URL then the Component associated with the Route Definition is instantiated and rendered in the Outlet. If the new Component contains routes of its own then a new Router (ChildRouter) is created for this Routing Component. The ChildRouter for the new Routing Component then attempts to match its Route Config against the parts of the URL that have not already been matched by the previous Router. This process continues until we run out of Routing Components or consume the entire URL.

Module

You can think of a module as a container for the different parts of your app – controllers, services, filters, directives, etc.

Module Loading & Dependencies

A module is a collection of configuration and run blocks which get applied to the application during the bootstrap process. In its simplest form the module consists of a collection of two kinds of blocks: Configuration blocks - There are some convenience methods on the module which are equivalent to the config block. Run block - Run blocks are the closest thing in Angular to the main method.

angular.module('myModule', []). config(function(injectables) { // provider-injector // This is an example of config block. // You can have as many of these as you want. // You can only inject Providers (not instances) // into config blocks. }). run(function(injectables) { // instance-injector // This is an example of a run block. // You can have as many of these as you want. // You can only inject instances (not Providers) // into run blocks });

Dependencies - Modules can list other modules as their dependencies. Asynchronous Loading - Modules are a way of managing $injector configuration, and have nothing to do with loading of scripts into a VM.

HTML Compiler

Angular's HTML compiler allows the developer to teach the browser new HTML syntax. The compiler allows you to attach behaviour to any HTML element or attribute and even create new HTML elements or attributes with custom behavior. Angular calls these behavior extensions directives. HTML has a lot of constructs for formatting the HTML for static documents in a declarative fashion. Angular comes pre-bundled with common directives which are useful for building any app. All of this compilation takes place in the web browser; no server side or pre-compilation step is involved.

AngularJS Compiler

Compiler is an Angular service which traverses the DOM looking for attributes. The compilation process happens in two phases.

Compile: traverse the DOM and collect all of the directives. The result is a linking function. Link: combine the directives with a scope and produce a live view. Any changes in the scope model are reflected in the view, and any user interactions with the view are reflected in the scope model.

How directives are compiled

It's important to note that Angular operates on DOM nodes rather than strings. Usually, you don't notice this restriction because when a page loads, the web browser parses HTML into the DOM automatically.

HTML compilation happens in three phases:

  1. $compile traverses the DOM and matches directives. If the compiler finds that an element matches a directive, then the directive is added to the list of directives that match the DOM element. A single element may match multiple directives.
  2. Once all directives matching a DOM element have been identified, the compiler sorts the directives by their priority. Each directive's compile functions are executed. Each compile function has a chance to modify the DOM. Each compile function returns a link function. These functions are composed into a "combined" link function, which invokes each directive's returned link function.
  3. $compile links the template with the scope by calling the combined linking function from the previous step. This in turn will call the linking function of the individual directives, registering listeners on the elements and setting up $watch with the scope as each directive is configured to do.

The result of this is a live binding between the scope and the DOM. So at this point, a change in a model on the compiled scope will be reflected in the DOM.

The difference between Compile and Link

At this point you may wonder why the compile process has separate compile and link phases. The short answer is that compile and link separation is needed any time a change in a model causes a change in the structure of the DOM. It's rare for directives to have a compile function, since most directives are concerned with working with a specific DOM element instance rather than changing its overall structure. *Directives often have a link function. A link function allows the directive to register listeners to the specific cloned DOM element instance as well as to copy content into the DOM from the scope.

Best Practice: Any operation which can be shared among the instance of directives should be moved to the compile function for performance reasons.

Understanding How Scopes Work with Transcluded Directives

One of the most common use cases for directives is to create reusable components.

transclude: true, scope: { title: '@', // the title uses the data-binding from the parent scope onOk: '&', // create a delegate onOk function onCancel: '&', // create a delegate onCancel function visible: '=' // set up visible to accept data-binding }, restrict: 'E', replace: true

Providers

Each web application you build is composed of objects that collaborate to get stuff done. These objects need to be instantiated and wired together for the app to work. In Angular apps most of these objects are instantiated and wired together automatically by the injector service. The injector creates two types of objects, services and specialized objects. Services are objects whose API is defined by the developer writing the service. Specialized objects conform to a specific Angular framework API. These objects are one of controllers, directives, filters or animations. The injector needs to know how to create these objects. You tell it by registering a "recipe" for creating your object with the injector. When an Angular application starts with a given application module, Angular creates a new instance of injector, which in turn creates a registry of recipes as a union of all recipes defined in the core "ng" module, application module and its dependencies. The injector then consults the recipe registry when it needs to create an object for your application.

Value Recipe

var myApp = angular.module('myApp', []); myApp.value('clientId', 'a12345654321x');

Factory Recipe

myApp.factory('clientId', function clientIdFactory() { return 'a12345654321x'; });

Service Recipe

myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);

Provider Recipe

myApp.provider('unicornLauncher', function UnicornLauncherProvider() { var useTinfoilShielding = false; this.useTinfoilShielding = function(value) { useTinfoilShielding = !!value; }; this.$get = ["apiToken", function unicornLauncherFactory(apiToken) { // let's assume that the UnicornLauncher constructor was also changed to // accept and use the useTinfoilShielding argument return new UnicornLauncher(apiToken, useTinfoilShielding); }]; }); myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) { unicornLauncherProvider.useTinfoilShielding(true); }]);

Constant Recipe

myApp.constant('planetName', 'Greasy Giant'); myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) { unicornLauncherProvider.useTinfoilShielding(true); unicornLauncherProvider.stampText(planetName); }]);

Bootstrap

The Angular initialization process and how we can manually initialize Angular if necessary.

Automatic Initialization

Angular initializes automatically upon DOMContentLoaded event or when the angular.js script is evaluated if at that time document.readyState is set to 'complete'. At this point Angular looks for the ngApp directive which designates your application root. If the ngApp directive is found then Angular will: load the module associated with the directive. create the application injector compile the DOM treating the ngApp directive as the root of the compilation. This allows you to tell it to treat only a portion of the DOM as an Angular application. Manual Initialization

If you need to have more control over the initialization process, you can use a manual bootstrapping method instead. Examples of when you'd need to do this include using script loaders or the need to perform an operation before Angular compiles a page.

Hello {{greetMe}}!

Deferred Bootstrap

This feature enables tools like Batarang and test runners to hook into angular's bootstrap process and sneak in more modules into the DI registry which can replace or augment DI services for the purpose of instrumentation or mocking out heavy dependencies.

If window.name contains prefix NG_DEFER_BOOTSTRAP! when angular.bootstrap is called, the bootstrap process will be paused until angular.resumeBootstrap() is called. angular.resumeBootstrap() takes an optional array of modules that should be added to the original list of modules that the app was about to be bootstrapped with.

Dev Dependencies:

  • Bower - client-side code package manager
  • Http-Server - simple local static web server
  • Karma - unit test runner
  • Protractor - end to end (E2E) test runner

Commands

  1. npm start : start a local development web-server
  2. npm test : start the Karma unit test runner
  3. npm run protractor : run the Protractor end to end (E2E) tests
  4. npm run update-webdriver : install the drivers needed by Protractor
  5. What's the difference between Karma and Protractor? When do I use which?

    Karma is a great tool for unit testing, and Protractor is intended for end to end or integration testing.

    This means that small tests for the logic of your individual controllers, directives, and services should be run using Karma. Big tests in which you have a running instance of your entire application should be run using Protractor. Protractor is intended to run tests from a user's point of view - if your test could be written down as instructions for a human interacting with your application, it should be an end to end test written with Protractor.

    Scope Data - Life Cycle Phases

    The scope data goes through the following life cycle phases:
    1. Creation
    2. Watcher Registration
    3. Model Mutation
    4. Mutation Observation
    5. Scope Destruction

    SCOPE: CREATION

    This phase initialised the scope. The root scope is created by the $injector when the application is bootstrapped. A digest loop is also created in this phase that interacts with the browser event loop.

    SCOPE: WATCHER REGISTRATION

    This phase registers watches for values on the scope that are represented in the template. These watches propagate model changes automatically to the DOM elements.

    SCOPE: MODEL MUTATION/CHANGED

    This phase occurs when data in the scope changes. The scope function $apply() updates the model and calls the $digest() function to update the DOM elements and registered watches.

    SCOPE: MODEL OBSERVATION

    This phase occurs when the $digest() function is executed by the digest loop at the end of $apply() call. When $digest() function executes, it evaluates all watches for model changes. If a value has been changed, $digest() calls the $watch listener and updates the DOM elements.

    SCOPE: DESTRUCTION

    This phase occurs when child scopes are no longer needed and these scopes are removed from the browser’s memory by using $destroy() function. It is the responsibility of the child scope creator to destroy them via scope.$destroy() API. This stop propagation of $digest calls into the child scopes and allow the memory to be reclaimed by the browser garbage collector.