The latest version of AngularJS is 1.8.3
If you are using Angular Framework higher versions you should first uninstall it from your computer
npm uninstall -g @angular/cli
Now we install the AngularJS in our laptop. Run this command
npm install -g angular@1.8.2
Initialize the project directory
mkdir angularjs-app
cd sample2_basic
We open VSCode with this command
code .
Then we initialize/create the package.json file ruuning this command (OPTIONAL)
npm init -y
We install angular dependencies with this command
npm install angular
We have an optinal way for installing the dependencies: bootstrap.css and angular.js files
We can create manually the folders structure (with mkdir) and then download the libraries with curl command
mkdir -p lib/bootstrap/dist/css
cd lib/bootstrap/dist/css
curl -O https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.css
mkdir -p lib/angular
cd lib/angular
curl -O https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.js
We create the folders and files project structure
We copy the following source code
app/index.html
<!doctype html>
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<title>My HTML File</title>
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css" />
<script src="lib/angular/angular.js"></script>
</head>
<body>
<p>Nothing here {{'yet' + '!'}}</p>
</body>
</html>
This is the code architecture explanation
To run the applicaton we right click on the index.html file and select the menu option Open with Live Server
This is the output we see
We will create a purely static HTML page and then examine how we can turn this HTML code into a template that AngularJS will use to dynamically display the same result with any set of data
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="app.css" />
<script src="lib/angular/angular.js"></script>
</head>
<body>
<ul>
<li>
<span>Nexus S</span>
<p>
Fast just got faster with Nexus S.
</p>
</li>
<li>
<span>Motorola XOOM™ with Wi-Fi</span>
<p>
The Next, Next Generation tablet.
</p>
</li>
</ul>
</body>
</html>
body {
padding-top: 20px;
}
This is the project folders and files structure
Now we can run the application and see the output
Now, it's time to make the web page dynamic — with AngularJS
We will also add a test that verifies the code for the controller we are going to add
For AngularJS applications, we encourage the use of the Model-View-Controller (MVC) design pattern to decouple the code and separate concerns
With that in mind, let's use a little AngularJS and JavaScript to add models, views, and controllers to our app
The list of three phones is now generated dynamically from data
This is the application source code
index.html
<html lang="en" ng-app="phonecatApp">
<head>
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<script src="lib/angular/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="PhoneListController">
<ul>
<li ng-repeat="phone in phones">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
</body>
</html>
app.css
body {
padding-top: 20px;
}
app.js
'use strict';
// Define the `phonecatApp` module
var phonecatApp = angular.module('phonecatApp', []);
// Define the `PhoneListController` controller on the `phonecatApp` module
phonecatApp.controller('PhoneListController', function PhoneListController($scope) {
$scope.phones = [
{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.'
}, {
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.'
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.'
}
];
});
app.spec.js
'use strict';
describe('PhoneListController', function() {
beforeEach(module('phonecatApp'));
it('should create a `phones` model with 3 phones', inject(function($controller) {
var scope = {};
var ctrl = $controller('PhoneListController', {$scope: scope});
expect(scope.phones.length).toBe(3);
}));
});
This is the application architecture
We first have to define the project dependencies in the package.json file
package.json
{
"name": "angularjs_official_sample_step4",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"bootstrap": "3.3.x"
},
"devDependencies": {
"angular": "^1.8.3",
"angular-mocks": "^1.8.3",
"jasmine-core": "^5.1.2",
"karma": "^6.4.3",
"karma-chrome-launcher": "^3.2.0",
"karma-jasmine": "^5.1.0",
"karma-ng-html2js-preprocessor": "^1.0.0"
}
}
To install the above dependencies we execute the command
npm i
or
npm install
The file package-lock.json file will be created
Also the libraries will be installed in node_modules
This is the project folders and files structure
To run the application we right click on the index.html file and selecting the Open with Live Server menu option
This is the application output
Install Karma CLI: Install Karma's command-line interface globally
npm install -g karma-cli
Then install the required dependencies:
npm install karma karma-jasmine jasmine-core karma-chrome-launcher karma-ng-html2js-preprocessor --save-dev
Create a Karma Configuration File:
karma init karma.conf.js
During the setup, answer the prompts as follows:
Testing framework: Jasmine
Use Require.js: No
Capture browsers: Chrome (you can add more browsers later)
Test result reporter: progress (or choose as per your preference)
Other questions: default options should be fine
Modify the karma.conf.js file to include your application and test files
// list of files / patterns to load in the browser
files: [
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
'app/*.js',
'app/*.spec.js'
],
To Run the Tests execute this command:
karma start karma.conf.js
This is the output we get
In the previous step, we saw how a controller and a template worked together to convert a static HTML page into a dynamic view
Since this combination (template + controller) is such a common and recurring pattern, AngularJS provides an easy and concise way to combine them together into reusable and isolated entities, known as components
AngularJS will create a so called isolate scope for each instance of our component
To create a component, we use the .component() method of an AngularJS module
This is the project folders and files structure
This is the applicaiton source code
index.html
<!DOCTYPE html>
<html lang="en" ng-app="phonecatApp">
<head>
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<script src="../node_modules/angular/angular.min.js"></script>
<script src="app.js"></script>
<script src="phone-list.component.js"></script>
</head>
<body>
<!-- Use a custom component to render a list of phones -->
<phone-list></phone-list>
</body>
</html>
app.js
'use strict';
angular.module('phonecatApp', []);
app.css
body {
padding-top: 20px;
}
phone-list.component.js
'use strict';
// Register `phoneList` component, along with its associated controller and template
angular.
module('phonecatApp').
component('phoneList', {
template:
'<ul>' +
'<li ng-repeat="phone in $ctrl.phones">' +
'<span>{{phone.name}}</span>' +
'<p>{{phone.snippet}}</p>' +
'</li>' +
'</ul>',
controller: function PhoneListController() {
this.phones = [
{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.'
}, {
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.'
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.'
}
];
}
});
phone-list.component.spec.js
'use strict';
describe('phoneList', function() {
// Load the module that contains the `phoneList` component before each test
beforeEach(module('phonecatApp'));
// Test the controller
describe('PhoneListController', function() {
it('should create a `phones` model with 3 phones', inject(function($componentController) {
var ctrl = $componentController('phoneList');
expect(ctrl.phones.length).toBe(3);
}));
});
});
This is the application arthitecture
We run the application and this is the output
We run the Tests and this is the output
We are going to refactor our codebase and move files and code around, in order to make our application more easily expandable and maintainable
-
Put each entity in its own file
-
Organize our code by feature area, instead of by function
-
Split our code into modules that other modules can depend on
This is the project folders and files structure
This is the application source code:
app/index.html
<!DOCTYPE html>
<html lang="en" ng-app="phonecatApp">
<head>
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<script src="../node_modules/angular/angular.min.js"></script>
<script src="app.module.js"></script>
<script src="phone-list/phone-list.module.js"></script>
<script src="phone-list/phone-list.component.js"></script>
</head>
<body>
<!-- Use a custom component to render a list of phones -->
<phone-list></phone-list>
</body>
</html>
app/app.module.js
'use strict';
// Define the `phonecatApp` module
angular.module('phonecatApp', [
// ...which depends on the `phoneList` module
'phoneList'
]);
app/app.css
body {
padding-top: 20px;
}
app/phone-list/phone-list.component.js
'use strict';
// Register `phoneList` component, along with its associated controller and template
angular.
module('phoneList').
component('phoneList', {
templateUrl: 'phone-list/phone-list.template.html',
controller: function PhoneListController() {
this.phones = [
{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.'
}, {
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.'
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.'
}
];
}
});
app/phone-list/phone-list.component.spec.js
'use strict';
describe('phoneList', function() {
// Load the module that contains the `phoneList` component before each test
beforeEach(module('phoneList'));
// Test the controller
describe('PhoneListController', function() {
it('should create a `phones` model with 3 phones', inject(function($componentController) {
var ctrl = $componentController('phoneList');
expect(ctrl.phones.length).toBe(3);
}));
});
});
app/phone-list/phone-list.module.js
'use strict';
// Define the `phoneList` module
angular.module('phoneList', []);
app/phone-list/phone-list.template.js
<ul>
<li ng-repeat="phone in $ctrl.phones">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
How to run the application
How to run the Tests
Modify the karma.conf.js file
...
// list of files / patterns to load in the browser
files: [
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
'app/phone-list/phone-list.module.js',
'app/app.module.js',
'app/phone-list/*.js', // to include all JS files in the phone-list folder
'app/**/*spec.js'
],
...
This is the test output
We will add full-text search. The phone list on the page changes depending on what a user types into the search box
We will also write an end-to-end (E2E) test
the data that a user types into the input box (bound to $ctrl.query
) is immediately available as a filter input in the list repeater (phone in $ctrl.phones | filter:$ctrl.query
)
This is the application source code:
phone-list.template.html
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
Search: <input ng-model="$ctrl.query" />
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
Container Fluid (<div class="container-fluid">
):
This div is a Bootstrap class that creates a full-width container
It ensures that the content inside it is responsive and spans the full width of the viewport
Row (<div class="row">
):
This div creates a horizontal group of columns using Bootstrap's grid system
Sidebar Column (<div class="col-md-2">
):
This div defines a column that takes up 2 out of 12 parts of the row (using Bootstrap's grid system for medium and larger devices)
Search Input (<input ng-model="$ctrl.query" />
):
This is an input field bound to the AngularJS model $ctrl.query. The ng-model directive binds the input value to the controller's query property, enabling two-way data binding.
Main Content Column (<div class="col-md-10">
):
This div defines a column that takes up the remaining 10 out of 12 parts of the row
Phone List (<ul class="phones">
):
This unordered list will display a list of phones
Repeating List Items (<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query">
):
The ng-repeat directive is used to iterate over each phone in the $ctrl.phones array
The filter:$ctrl.query part filters the phones based on the query input. It only displays phones that match the query
Phone Name and Snippet (<span>{{phone.name}}</span>, <p>{{phone.snippet}}</p>
):
{{phone.name}} and {{phone.snippet}} are AngularJS expressions that display the phone's name and snippet, respectively
Functionality
The search input allows the user to type a query
As the user types, the ng-model directive updates the $ctrl.query property
The ng-repeat directive dynamically filters the list of phones based on the query
Only phones whose properties match the query are displayed in the list
This is the application architecture
This is the application output
- Install Protractor: Make sure you have Protractor installed. If not, you can install it globally using npm
npm install -g protractor
- Download your Chromedriver
Navigate to this web page and download the latest chromedriver version
https://googlechromelabs.github.io/chrome-for-testing/#stable
We select the stable version link
Then we download the windows 64 version
Unzip the file and copy in a folder
Set the PATH environmental variable
Run the Task Manager and delete chromedriver.exe proccess
Also delete the chromedriver.exe file from the path:
.../AppData/Roaming/npm/node_modules/protractor/node_modules/webdriver_manager/selenium/chromedriver_114.0.57
- Update WebDriver: Protractor uses WebDriver to interact with your web application. Make sure you update WebDriver to get the latest version
webdriver-manager update
- Start WebDriver: Before running your tests, you need to start the WebDriver server
webdriver-manager start
Open one Terminal Window in VSCode and run the above command
- Protractor Configuration File: Ensure you have a Protractor configuration file (protractor.conf.js)
This file tells Protractor where to find your tests and how to run them. Here’s an example configuration file:
protractor.conf.js
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['e2e-tests/*.js'], // Adjust the path to your test files as needed
capabilities: {
'browserName': 'chrome'
},
framework: 'jasmine',
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
},
onPrepare: function() {
browser.ignoreSynchronization = true; // Disable synchronization globally
},
chromeDriver: 'C:/chromedriver/chromedriver.exe' // Specify the path to the new ChromeDriver
};
e2e-tests/scenarios.js
'use strict';
describe('PhoneCat Application', function() {
describe('phoneList', function() {
beforeEach(function() {
browser.ignoreSynchronization = true; // Disable Angular synchronization
browser.get('http://127.0.0.1:5500/app/index.html');
});
it('should filter the phone list as a user types into the search box', function() {
var phoneList = element.all(by.repeater('phone in $ctrl.phones'));
var query = element(by.model('$ctrl.query'));
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
});
});
- Run the Test: In VSCode, you can run your e2e test using the integrated terminal
Open a second Terminal Window in VSCode and run this command
protractor protractor.conf.js
We will add a feature to let our users control the order of the items in the phone list
The dynamic ordering is implemented by creating a new model property, wiring it together with the repeater, and letting the data binding magic do the rest of the work
The application now displays a drop-down menu that allows users to control the order in which the phones are listed
This is the application source code
app/phone-list/phone-list.component.js
'use strict';
// Register `phoneList` component, along with its associated controller and template
angular.
module('phoneList').
component('phoneList', {
templateUrl: 'phone-list/phone-list.template.html',
controller: function PhoneListController() {
this.phones = [
{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.',
age: 1
}, {
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.',
age: 2
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.',
age: 3
}
];
this.orderProp = 'age';
}
});
app/phone-list/phone-list.component.spec.js
'use strict';
describe('phoneList', function() {
// Load the module that contains the `phoneList` component before each test
beforeEach(module('phoneList'));
// Test the controller
describe('PhoneListController', function() {
it('should create a `phones` model with 3 phones', inject(function($componentController) {
var ctrl = $componentController('phoneList');
expect(ctrl.phones.length).toBe(3);
}));
});
});
app/phone-list/phone-list.template.html
<div class="col-md-2">
<!--Sidebar content-->
<p>
Search:
<input ng-model="$ctrl.query" />
</p>
<p>
Sort by:
<select ng-model="$ctrl.orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</p>
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
e2e-tests/scenarios.js
'use strict';
// AngularJS E2E Testing Guide:
// https://docs.angularjs.org/guide/e2e-testing
describe('PhoneCat Application', function() {
describe('phoneList', function() {
beforeEach(function() {
browser.get('index.html');
});
it('should filter the phone list as a user types into the search box', function() {
var phoneList = element.all(by.repeater('phone in $ctrl.phones'));
var query = element(by.model('$ctrl.query'));
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
it('should be possible to control phone order via the drop-down menu', function() {
var queryField = element(by.model('$ctrl.query'));
var orderSelect = element(by.model('$ctrl.orderProp'));
var nameOption = orderSelect.element(by.css('option[value="name"]'));
var phoneNameColumn = element.all(by.repeater('phone in $ctrl.phones').column('phone.name'));
function getNames() {
return phoneNameColumn.map(function(elem) {
return elem.getText();
});
}
queryField.sendKeys('tablet'); // Let's narrow the dataset to make the assertions shorter
expect(getNames()).toEqual([
'Motorola XOOM\u2122 with Wi-Fi',
'MOTOROLA XOOM\u2122'
]);
nameOption.click();
expect(getNames()).toEqual([
'MOTOROLA XOOM\u2122',
'Motorola XOOM\u2122 with Wi-Fi'
]);
});
});
});
This is the application architecture
How to run the application