AngularJS in Depth – Part 1
AngularJS from Basics to Dependency Injection
AngularJS is a MVC or (MV*) framework for client side web applications. One of the basic functionality offered by AngularJS is the javascript like expression evaluation. Expressions are usually placed inside the double curly braces as shown below.
<html><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script><script src="app.js"></script></head><body> <div>
1 plus 2 is {{ 1 + 2 }}</div></body></html>
AngularJS is now officially called a MVW framework. W stands for whatever
Although we have linked to the AngularJS library in the above HTML snippet, the expression evaluation will not work as AngulrJS need to be bootstrapped. We will place the angular bootstrapping code inside the app.js file as follows.
angular.element(document).ready(function(){
angular.bootstrap(document);
});
The inclusion of AngularJS script makes the angular namespace object available globally. Several utility functions are available on this angular global namespace. One of the global utility function named element wraps the given element as a JQuery element. Inside the ready handler we will use bootstrap utility method to initialize the AngularJS application on the document.
Although the manual initialization is working in our example, it deviates from the declarative philosophy of AngularJS. AngularJS recommends declarative programming for building UIs and wiring software components, and imperative programming for expressing business logic. Directives are the AngularJS way to create declarative building blocks. One of the built in directive, ngApp provides the automatic initialization for AngularJS application. So our previous manual configuration could be expressed in HTML alone as follows.
<html ng-app><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script> </head><body><div>
1 plus 2 is {{ 1 + 2 }}</div></body></html>
In Javascript, usually the evaluation context will be window. In contrast, the execution context of an AngularJS expression is scope. scope is a naive concept in AngularJS that glues the view and the controller. Whatever properties we attach on the scope object becomes the model part. Next, let’s use expression against a non-existing model named message of string type.
if jquery is not included in the page, angularjs will fall back to its own implementation of the subset of jquery, called jqlite
The scope and its controller function interaction transformed the AngularJS from its MVC root to a MVVM like framework
<html ng-app><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script> </head><body> <div>
The message is: {{message}}</div></body></html>
We have not defined the message property on the $rootScope and instead of throwing an error, the expression fails silently causing nothing to display for the message. Although the above expression is simple, a lot of functionality is thrown to us by the framework. AngularJs has compiled our html along with the expression and has produced what is known as a live view. It is live view because any change in model (through the scope) will be propagated to the view and any changes in view will be propagated to the model. This is two way databinding. We will test the model to view databinding first and through the console to understand how to change the model imperatively. When the following is executed through the console, the message will be displayed in the view automatically.
var injector = angular.element(document).injector();
var rootScope = injector.get("$rootScope");
rootScope.$apply(function(){rootScope.message = "Hello World!";})
Although we have talked about $rootScope we have not got hold of it yet. The $rootScope is not exposed by the AngularJS to the outside. AngularJS uses Dependency Injection and we can ask the injector for known services or objects to it (this is for demonstration purpose only. AngularJS will automatically fulfil our dependencies as we see later). We cant directly change the model from outside. Instead we have to use the $apply method on the scope to change the model and make AngularJS aware of it.
ngModel directive is used for propagating the view change to the model as follows.
<html ng-app><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script> </head><body> <div>
The message is: {{message}}<br><input ng-model="message"></div></body></html>
Managing an application with directives and $rootScope alone will be cumbersome. The solution is controllers. Controller augments the $scope object with initial properties and behaviors. The first requirement for a controller is the scope object and for that we can use the ngController directive.
<html ng-app><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script><script src="app.js"></script> </head><body> <div ng-controller="HelloController">
The message is: {{message}}<br><input ng-model="message"></div></body></html>
function HelloController($scope){
$scope.message = "Hello World!";
}
ngController, behind the scene creates a new scope inherited from the current scope ($rootScope here)through the $new method on the current scope. This newly created scope is then applied (using the javascript apply) against the specified controller function. When further directives and controllers are added, the scopes will imitate the DOM like tree structure with $rootNode as the base.
The controller we have just created lives in the global namespace and it feels like entirely detached from the AngularJS. The solution is to use the concept of modules. AngularJS is a web application framework and modules are the building blocks of well structured AngularJS application. A large scale AngularJS application may contain several modules like service, directive, and filter modules. It is the duty of an application level module to co-ordinate the different modules. In contrast to this pattern, an AngularJS application may start its life as a single application level module.
var app = angular.module("messageApp", []);
app.controller("HelloController", function HelloController($scope){
$scope.message = "Hello World!";
console.log($scope.$parent);
});
The inclusion of AngularJS script makes the angular namespace object available globally. A utility method on angular namespace object called module makes it easy to create a new module. The first parameter to the module method is the name of the module and if the second parameter of dependencies array is provided, it will create a module. If the second parameter is omitted, module method will try to retrieve the module of given name instead of creating a new one so we are passing an empty array as our first module does not have any dependencies.
Now our application is completely broken as our newly created module is not loaded by AngularJS. The fix is trivial, as ngApp directive accepts the module name to load during the initialization process.
<html ng-app="messageApp"><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script><script src="app.js"></script> </head><body> <div ng-controller="HelloController">
The message is: {{message}}<br><input ng-model="message"></div></body></html>
Back to our javascript code, AngularJS is automatically fulfilling our request of $scope through the facilities of Dependency Injection. Behind the scenes, AngularJS is using the injector to satisfy the requirements. Let’s ask for a thing that the AngularJS injector is not aware of, say the time at which our application has started.
var app = angular.module("messageApp", []);
app.controller("HelloController", function HelloController($scope, startTime){
$scope.message = "Hello World!";
console.log($scope.$parent);
});
startTime is our requirement or dependency that AngularJS is not aware of and hence it responds by the error message Error: Unknown provider: startTimeProvider <- startTime. The error message tells that AngularJS does not know how to cook a startTime object and we have to give the recipe of creating startTime. The simplest solution here is to create startTime ourself and then pass it to the AngularJS. value method on the module is just for that purpose.
var app = angular.module("messageApp", []);
app.value("startTime", new Date());
app.controller("HelloController", function HelloController($scope, startTime){
$scope.message = "Hello World!";
console.log($scope.$parent);
$scope.startedTime = startTime;
});
<html ng-app="messageApp"><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script><script src="app.js"></script> </head><body> <div ng-controller="HelloController">
The message is: {{message}}<br><input ng-model="message"><br>
Application Started At: {{startedTime}}</div></body></html>
Any object created by the injector is known as service. In the above code $scope and startTime are examples for services. Any service managed by AngularJS requires a unique name and that is why we have to pass the name as first parameter for the value method. value method is not that powerful as we ourself are creating the object and if this object has dependencies we have to fulfil them ourselves.
When running the above code our time display appears to be cryptic and this is a good time to introduce the concept of filters in AngularJS.
<html ng-app="messageApp"><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script><script src="app.js"></script> </head><body> <div ng-controller="HelloController">
The message is: {{message}}<br><input ng-model="message"><br>
Application Started At: {{startedTime | date:'medium'}}</div></body></html>
Filters are used to format expressions. In the above case, date filter has formatted the data. For formatting the date we have passed ‘medium’ as the parameter to date filter by using the : as separator. Now we will update the application by displaying the elapsed seconds since starting the application.
<html ng-app="messageApp"><head><script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script><script src="app.js"></script> </head><body> <div ng-controller="HelloController">
The message is: {{message}}<br><input ng-model="message"><br>
Application Started At: {{startedTime | date:'medium'}}<br>
Elapsed: {{ elapsed }} seconds
</div></body></html>
var app = angular.module("messageApp", []);
app.value("startTime", new Date());
app.controller("HelloController", function HelloController($scope, startTime){
$scope.message = "Hello World!";
console.log($scope.$parent);
$scope.startedTime = startTime;
var intervalId;
intervalId = setInterval(function(){
$scope.$apply(function(){
var elapsedMilliSeconds = (new Date().getTime() - $scope.startedTime.getTime());
$scope.elapsed = Math.round(elapsedMilliSeconds / 1000);
});
}, 2000);
});
We are using the plain old setInterval method of javscript and wrapping the elapsed time assignment inside the $apply method of the scope object in order to convey the assignment to the AngularJS application. Although the example works fine, we are measuring the time when the javascript file is loaded and not the actual start up of the AngularJS application. Module expose config and run methods to hook in to the configuration phase and aplication startup phase. We will revert the code to utilize these hooks.
var app = angular.module("messageApp", []);
app.controller("HelloController", function HelloController($scope, startTime){
$scope.message = "Hello World!";
console.log($scope.$parent);
$scope.startedTime = startTime;
var intervalId;
intervalId = setInterval(function(){
$scope.$apply(function(){
var elapsedMilliSeconds = (new Date().getTime() - $scope.startedTime.getTime());
$scope.elapsed = Math.round(elapsedMilliSeconds /1000);
});
}, 2000);
});
//app.run(function($provide){
// $provide.value("startTime", new Date()); // Will not work as $provide could not be injected in run phase
//});
app.config(function($provide){
$provide.value("startTime", new Date());
});
We are now exposed to differences of run and config blocks. We could not inject $provide to run handler as it is too late to provide recipe for object creation. Only values and constants could be injected in run phase. So we will override the service value in run phase, instead of using the $provide.
var app = angular.module("messageApp", []);
app.controller("HelloController", function HelloController($scope, startTime){
$scope.message = "Hello World!";
console.log($scope.$parent);
$scope.startedTime = startTime;
var intervalId;
intervalId = setInterval(function(){
$scope.$apply(function(){
var elapsedMilliSeconds = (new Date().getTime() - $scope.startedTime.getTime());
$scope.elapsed = Math.round(elapsedMilliSeconds /1000);
});
}, 2000);
});
app.run(function(startTime){
// startTime = new Date(); re-assignment is not going to work as pointed out by llia choly in comments
startTime.setTime((new Date()).getTime()); // call setTime method to update the new time
});
app.config(function($provide){
$provide.value("startTime", new Date());
});
Another way to provide recipe of object creation is the constant method. We will introduce MILLIS_IN_SECONDS constant to our application. One distinguishing feature of constant is that it could be injected in to the configuration phase which is not possible with the values.
var app = angular.module("messageApp", []);
app.controller("HelloController", function HelloController($scope, startTime, MILLIS_IN_SECONDS){
$scope.message = "Hello World!";
$scope.startedTime = startTime;
var intervalId;
intervalId = setInterval(function(){
$scope.$apply(function(){
var elapsedMilliSeconds = (new Date().getTime() - $scope.startedTime.getTime());
$scope.elapsed = Math.round(elapsedMilliSeconds / MILLIS_IN_SECONDS);
});
}, 2000);
});
app.run(function(startTime){
//startTime = new Date();
startTime.setTime((new Date()).getTime());
});
app.config(function($provide, MILLIS_IN_SECONDS ){
$provide.value("startTime", new Date());
console.log(MILLIS_IN_SECONDS);
});
app.constant("MILLIS_IN_SECONDS", 1000);
When we have an object creation function, factory is the way to register that recipe. With the factory method we register a factory along with a name. A factory is just a method used to create an object.
var app = angular.module("messageApp", []);
app.controller("HelloController", function HelloController($scope, startTime, elapsedEstimator){
$scope.message = "Hello World!";
console.log($scope.$parent);
$scope.startedTime = startTime;
var intervalId;
intervalId = setInterval(function(){
$scope.$apply(function(){
$scope.elapsed = elapsedEstimator.elapsed();
});
}, 2000);
});
app.run(function(startTime){
//startTime = new Date();
startTime.setTime((new Date()).getTime());
});
app.config(function($provide, MILLIS_IN_SECONDS ){
$provide.value("startTime", new Date());
console.log(MILLIS_IN_SECONDS);
});
app.constant("MILLIS_IN_SECONDS", 1000);
app.factory("elapsedEstimator", function(startTime, MILLIS_IN_SECONDS){
return {
elapsed: function(){
var elapsedMilliSeconds = (new Date().getTime() - startTime.getTime());
var elapsed = Math.round(elapsedMilliSeconds / MILLIS_IN_SECONDS);
return elapsed;
}};
});
Since function is first class citizen in javascript we can register a function returning function as a factory also. factory method is the most widely used dependency registration mechanism in AngularJS since it is very flexible.
It is time to examine the final dependency registration utility method service.
var app = angular.module("messageApp", []);
app.controller("HelloController", function HelloController($scope, startTime, elapsedEstimator){
$scope.message = "Hello World!";
console.log($scope.$parent);
$scope.startedTime = startTime;
var intervalId;
intervalId = setInterval(function(){
$scope.$apply(function(){
$scope.elapsed = elapsedEstimator.elapsed();
});
}, 2000);
});
app.run(function(startTime){
//startTime = new Date();
startTime.setTime((new Date()).getTime());
});
app.config(function($provide, MILLIS_IN_SECONDS ){
$provide.value("startTime", new Date());
console.log(MILLIS_IN_SECONDS);
});
app.constant("MILLIS_IN_SECONDS", 1000);
app.service("elapsedEstimator", ElapsedEstimator );
function ElapsedEstimator(startTime, MILLIS_IN_SECONDS ){
this.elapsed = function(){
var elapsedMilliSeconds = (new Date().getTime() - startTime.getTime());
var elapsed = Math.round(elapsedMilliSeconds / MILLIS_IN_SECONDS);
return elapsed;
}
}
ElapsedEstimator constructor has two dependencies and hence it could not be registered as a value (startTime parameter will be available only later in the lifecycle of the application). This constructor could be easily registered by service method and all of its dependencies will be fulfilled automatically.
Expressions in AngularJS are fail tolerant. They will not throw exceptions when you access undefined properties.
Accessing the injector and using it is done here for demonstration purpose only. In production level code such practice is a code smell or even an anti pattern
ngModel directive is used for two way data binding in AngularJS
If the property given by the expression is not there in the scope, ngModel will create one on the scope
Simply stated, ngModel is capable of creating the data property on the scope (if there is not one). Similarly, ngController is capable of assigning behavior to the scope. It could specify the controller that should be in charge of the given scope.
WARNING: If the second parameter of dependencies array for module utility method is omitted AngularJS will try to retrieve an existing one (which will not the expected outcome here)
ngApp directive is used for auto bootstrapping the AngularJS application
value method is used for regsitering a pre created dependency.
Filters are typically used in formatting the expressions in templates. Colon (:) delimter is used for passing single and multiple arguments to the filters
setInterval calls a given function repeatedly with a given fixed time delay
Only providers and constants could be injected into configuration block while only constants and instances could be injected into the run block
$provide service is used to register new providers for the $injector.
First class function means, function could be treated as a value. In javascript, function is first class so it could be passed as a parameter or returned as a value. Term First class function along with currying was coined by Christopher Strachey