Recently my attention was focused on a very interesting JavaScript framework named
AngularJS. It is a framework created by Google to augment creation of rich browser-based application. In this article I would like to present an example of application created with AngularJS that will use some of the features provided by this framework. Of course I've just began to learn AngularJS so this article contains examples of basic concepts used by AngularJS.
1. Application concept
Our application will be a very simple calculator, that provides the four basic mathematical operations, and it will work on integer numbers. The expected layout should be like that:
2. Download AngularJS framework
AngularJS framework is distributed as a single JavaScript file and can be acquired on the
angularjs.org page. Just choose
stable and
mininified version of the file. It will be named angular.min.js. When I was writing this article the stable version was 1.0.7.
3. Create an application's structure
Our application will be organized in this directory structure:
Calculator Main project directory
|--lib
| |--js JavaScript 3rd party libraries
|--app
|--js JavaScript application source files
|--style CSS files
|--view HTML files
When the application's structure is created, then we copy downloaded
angular.min.js file into
Calculator/lib/js directory.
Then let's create basic source files that will define our application. We'll need three of them:
- Calculator/index.html - root view layer file of our application
- Calculator/app/js/app.js - main source file, will put logic in this file
- Calculator/app/style/default.css - main cascading style file
4. Create AngularJS application
In next step we'll create an html skeleton for our application that will define basic html properties and include our source files. So the content of
index.html should look like that:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html ng-app="calculatorApplication">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="lib/js/angular.min.js" type="text/javascript"></script>
<script src="app/js/app.js" type="text/javascript"></script>
<link href="app/style/default.css" rel="stylesheet" type="text/css">
</head>
<body>
<div ng-view></div>
</body>
</html>
Beside the standard html header there are two elements that are specific to AngularJS application:
- <html ng-app="calculatorApplication">
It tells AngularJS that everything inside html tag is an AngularJS controlled application. And it is a module called "calculatorApplication".
- <div ng-view></div>
This ng-view attribute points to the element that will be a container, where AngularJS will put created view.
As you can see a framework extends a standard HTML with its' own attributes that can be interpreted by AngularJS internals.
After creating HTML structure of our application we have to add some behavior. We'll do that by editing
app.js file and adding there this part of code:
var app = angular.module("calculatorApplication", []);
This construct will create AngularJS module called
calculatorApplication. Empty square brackets stands for empty list of dependencies that our application will require.
In the next step we would like to define some basic behavior for our application. Basically we want our application to have a home page that will have a welcome message and a link to our calculator.
That page should look like that:
As you can see the url for this page have an interesting
#/home suffix. Basically it is an address of our application subpage. This is a standard way to define subpages in AngularJS. Now we would like to bind this
/home suffix with some content. So let's add a controller definition of this page by changing
app.js content into this:
var app = angular.module("calculatorApplication", []).config(function ($routeProvider) {
$routeProvider.when("/home", {
templateUrl: "app/view/home.html",
controller: "HomeController"
});
$routeProvider.otherwise({
redirectTo: "/home"
});
});
app.controller("HomeController", function () {
});
A few things are happening in this part of code. First we've called config method and passed a function that will configure our application. This function takes a parameter
$routeProvider which is responsible for binding url address (ie.
/home suffix) with a proper behavior. In our case we want it to use a
home.html template for the view and
HomeController when there is
/home suffix present. In any other case we want our application to redirect to that address. Also we've defined a controller that is called
HomeController with no specific behavior.
The last thing we need to do is to create a template for our home page. So create a file
Calculator/app/view/home.html and put this content inside it:
<div>
<div>
This is home page:
</div>
<div>
You can go to <a href="#/calculator">Calculator</a>.
</div>
</div>
As you can see it is very simple html code that will display a message and a link to the calculator page (the page we havn't created yet).
5. First try out of an application
Now you can try to run this application. You can do this in two ways:
- Deploy it on http server and go to url of index.html file
- Open index.html directly from your file system
For now, the latter one will be an easier solution ;)
Attention!
Because of security reasons some browsers can block a javascript to access local files through AJAX request. This is the case at least for a Chrome and an Internet Explorer. I don't know the solution for an IE but if you want to test it in a Chrome you have to run the browser with
--allow-file-access-from-files parameter as stated on this
stackoverflow question. It should not be the case if you deploy your application in http server.
For a Firefox browser our application should work flawlessly.
6. Add calculator page
To add a calculator to our application first we need to create an html template that will define ours calculator's view. We'll put it inside
Calculator/app/view/calculator.html file:
<div class="calculator">
<div class="display">0</div>
<div class="digitsKeyboard">
<div class="digitKey">1</div>
<div class="digitKey">2</div>
<div class="digitKey">3</div>
<div class="digitKey">4</div>
<div class="digitKey">5</div>
<div class="digitKey">6</div>
<div class="digitKey">7</div>
<div class="digitKey">8</div>
<div class="digitKey">9</div>
<div class="digitKey">0</div>
<div class="equalSignKey">=</div>
</div>
<div class="operationsKeyboard">
<div class="operationKey">/</div>
<div class="operationKey">*</div>
<div class="operationKey">+</div>
<div class="operationKey">-</div>
</div>
</div>
So basically in our calculator we'll have a display, a keyboard with digit keys and an equal sign key, and a keyboard with possible mathematical operations. We also have to provide a stylesheet for our calculator so it can look properly. So put this CSS inside
Calculator/app/style/default.css file:
.calculator {
display: inline-block;
background-color: black;
height: 240px;
width: 208px;
overflow: hidden;
}
.display {
background-color: #DDDDDD;
border: 1px solid black;
font-family: monospace;
font-size: 27px;
font-weight: bold;
height: 30px;
padding: 0 4px;
text-align: right;
width: 198px;
overflow: hidden;
}
.digitsKeyboard {
overflow: hidden;
width: 156px;
float: left;
}
.digitKey {
width: 50px;
height:50px;
background-color: #AABBCC;
}
.equalSignKey {
width: 102px;
height: 50px;
background-color: #AACCBB;
}
.operationsKeyboard {
overflow: hidden;
width: 52px;
height:208px;
float: left;
}
.operationKey {
width: 50px;
height:50px;
background-color: #CCAABB;
}
.digitKey, .equalSignKey, .operationKey {
cursor: pointer;
font-family: monospace;
font-size: 33px;
border: 1px solid black;
line-height: 50px;
text-align: center;
float: left;
}
.digitKey:hover, .equalSignKey:hover, .operationKey:hover {
opacity: 0.8;
}
.digitKey:active, .equalSignKey:active, .operationKey:active {
opacity: 0.7;
}
The last thing we have to do is to map a
/calculator address in our application. So we need to add a calculator page to
$routeProvider in exactly the same way we've defined a home page. So add this inside
Calculator/app/js/app.js file:
$routeProvider.when("/calculator", {
templateUrl: "app/view/calculator.html",
controller: "CalculatorController"
});
And also define new empty controller for Calculator:
app.controller("CalculatorController", function () {
});
Now you can try to access calculator page in your browser. Just open the
index.html file and click on the calculator link. Then you should see a calculator on the screen.
7. Define calculator constants
Now we'll add some juice to our
CalculatorController. To do that let's start by changing the factory function declaration for the controller:
app.controller("CalculatorController", function ($scope) {
});
As you can see we've added a
$scope parameter to a function definition. It is an object where our application state is held. For our purpose you just need to know that you can define any field inside the $scope object and it will be accessible in other places in an application.
So let us start with defining a keys of calculator with the required properties by putting this code inside controller's function.
$scope.digitKeys = [
{label: "1", value: 1}, {label: "2", value: 2}, {label: "3", value: 3},
{label: "4", value: 4}, {label: "5", value: 5}, {label: "6", value: 6},
{label: "7", value: 7}, {label: "8", value: 8}, {label: "9", value: 9},
{label: "0", value: 0}
];
We'll use
digitKeys field to define an array of objects that represent every digit key in calculator keyboard. Every key has a
label property that will be displayed on given key and a value that will be used for computation.
$scope.equalSignKey = {label: "="};
Here we only need a label for equal sign key. This code is placed here only to be consistent with other keys definitions. Other solution would be just to place equal sign directly inside html file.
$scope.operationKeys = [
{label: "/", operation: function (a, b) {return a / b}},
{label: "*", operation: function (a, b) {return a * b}},
{label: "+", operation: function (a, b) {return a + b}},
{label: "-", operation: function (a, b) {return a - b}}
];
And the last set of keys are the mathematical operation keys. As you can see we put the operation function inside every object, so that will give us easy access to them, when we'll want to call it.
8. Make calculator rendered dynamically
When we defined our buttons in CalculatorController we can use them to simplify our html template and generate buttons dynamically. Let's start with the easiest case of equal sign key:
<div class="equalSignKey">
{{ equalSignKey.label }}
</div>
In this case we used
{{ }} placeholder to tell AngularJS that we want to put
equalSignKey.label value in this place. Angular will look for that value within the
$scope object and it will bind it with a corresponding part of an html file.
We'll do similar thing with digit buttons, but we'll use an AngularJS directive named
ng-repeat that allow us to iterate over array elements and repeat an html tag for every element in array:
<div class="digitKey" ng-repeat="key in digitKeys">
{{ key.label }}
</div>
In this case AngularJS iterates over
$scope.digitKeys array and puts current element inside a key variable. That way we don't have to repeat html code for every key.
We'll do exactly the same operation for operations keys:
<div class="operationKey" ng-repeat="key in operationKeys">
{{ key.label }}
</div>
So the resulting
calculator.html file should look like that:
<div class="calculator">
<div class="display">0</div>
<div class="keyboard">
<div class="digitKey" ng-repeat="key in digitKeys">
{{ key.label }}
</div>
<div class="equalSignKey" ng-click="compute()">
{{ equalSignKey.label }}
</div>
</div>
<div class="operations">
<div class="operationKey" ng-repeat="key in operationKeys">
{{ key.label }}
</div>
</div>
</div>
Now you can test the application to check if it still displays properly.
9. Add calculator behavior
Now we want to make our calculator to behave as real calculator so we'll need to define a variables that will hold a current calculator state. We'll need 5 of those:
- displayValue - current value displayed on calculator screen
- valueA - first (left) value that will be used for computation
- valueB - second (right) value that will be used for computation
- selectedOperation - which mathematical operation was chosen by the user
- clearValue - should value displayed on screen be cleared after new digit pressed?
So let's define them and initialize them with default values. Add this code inside a
CalculatorController:
$scope.displayValue = 0;
$scope.valueA = 0;
$scope.valueB = 0;
$scope.selectedOperation = null;
$scope.clearValue = true;
10. Bind displayValue variable with view
As we now have a variable
displayValue, we would like it to be displayed in our calculator. And in this place best of AngularJS comes in. To do that just put a
{{ displayValue }} placeholder inside a
calculator.html changing
<div class="display">0</div> so it will look like this:
<div class="display">{{ displayValue }}</div>
That's all!
Now AngularJS we'll do it's tricks to ensure that value displayed on page is equal to the value of the variable. If you change the variable value, then automatically the value displayed on the page will be changed to be the same as the variable.
And that's not all, imagine if display would be editable input field, then when user would input some value inside it, then automatically the value of the binded variable would be changed as well.
So it works both ways and it's hugely simplifies development of an application.
11. Add behavior to calculator's buttons
We have all parts of our calculator in place, so now it's time to add a behavior to it. So the main idea is to bind a buttons with correct behavior. To do this with AngularJS we'll use
ng-click directive. So let's change the
calculator.html template:
<div class="calculator">
<div class="display">{{ displayValue }}</div>
<div class="digitsKeyboard">
<div class="digitKey" ng-repeat="key in digitKeys"
ng-click="digitClicked(key.value)">
{{ key.label }}
</div>
<div class="equalSignKey" ng-click="compute()">
{{ equalSignKey.label }}
</div>
</div>
<div class="operationsKeyboard">
<div class="operationKey" ng-repeat="key in operationKeys"
ng-click="operationClicked(key.operation)">
{{ key.label }}
</div>
</div>
</div>
In this part of code we've added three
ng-click directives that contains simple JavaScript function calls:
- For a digit key: ng-click="digitClicked(key.value)"
- For an operation key: operationClicked(key.operation)"
- For an equal sign key: ng-click="compute()"
As you can see we could pass a data defined earlier in AngularJS variables in the same way as we've passed them earlier into
{{ }} blocks. The main idea for those changes is to call a corresponding function after clicking on a button.
Now let's define those three functions inside
CalculatorController. It is important to notice that those functions have to be defined as a part of
$scope object:
- Function to handle digit button click:
$scope.digitClicked = function (digit) {
if ($scope.clearValue) {
$scope.displayValue = digit;
$scope.clearValue = false;
} else {
$scope.displayValue = $scope.displayValue * 10 + digit;
}
$scope.valueB = $scope.displayValue
};
After user clicks a digit button, we want to update displayed value by replacing displayed value with digit, or by increasing displayed value by that digit (it's simple, just scale previous value by the factor of 10 and then add clicked digit). Also we need to remember displayed value as second value for the mathematical operation.
- Function to handle operation button click:
$scope.operationClicked = function (operation) {
$scope.selectedOperation = operation;
$scope.valueA = $scope.displayValue;
$scope.valueB = $scope.displayValue;
$scope.clearValue = true;
};
When user clicks operation button we simply want to remember clicked operation, and assign currently displayed value as both values for mathematical operation. Also we need to set
clearValue flag, so when user clicks on digit button, the displayed value will be replaced by selected digit, not increased by it.
- Function to handle equal sign button click:
$scope.compute = function () {
if($scope.selectedOperation != null) {
$scope.displayValue = Math.floor(
$scope.selectedOperation($scope.valueA, $scope.valueB));
$scope.clearValue = true;
$scope.valueA = $scope.displayValue;
}
}
The last function main responsibility is to calculate a result if an operation is selected (assigned to
$scope.selectedOperation field). It is done by simple call of remembered function. Also
clearValue flag is set and a computation result is remembered as first value of new operation.
Summary
This is all, now we have a fully functional calculator for internet browsers.
As you can see AngularJS is an easy to use (at least for an online calculator) and quite powerful framework for JavaScript application development. It brings a new way to bind an application state with html based view layer, that greatly simplifies creation of JavaScript applications.
The complete source code of this calculator is available at
github.com.