Source: AngularJS: Get Started, K. Scott Allen (PluralSight)
https://angularjs.org
AngularJS is a Javascript library written in Javascript and distributed as a Javascript file. The Javascript file can be downloaded or loaded via CDN.
To use an external module, e.g ngAnimate
, https://code.angularjs.org/1.2.32/angular-animate.js, you must include the module as dependency in your app.
let myApp = angular.module('myApp', ['ngAnimate']);
Example AngularJs directives
ng-app, ng-controller, ng-show
<div ng-controller='mainController'></div>
myApp.controller('studentsController', function($scope, $route, $otherServices) { //... });
Before an Angular app is compiled, the DOM must load first and fire the onContentReady() event.
ng-app
directive on the DOMng-app
directiveDigest and apply are central to how Angular knows if anything has changed in the App or DOM. $Digest loop is the equivalent of JS event loop.
$digest
processes all watchers on the current scope$apply
is used to notify that something has changed outside the AngularJs domain (e.g jQuery changed something on page). $apply then forces a $digest cycle.$scope glues Controllers and Views together. The controller constructs the model on $scope and provides commands for the View to act upon. $scope provides context.
Best Practice
myApp.controller('mainController', function ($log, $scope, $filter){});
where scope, log and filter are services being injected into the mainController.Used for linking urls to Controllers and Views. Define routes using $routeProvider
. Typically used in conjunction with ngView
directive and $routeParams
service.
To indicate that an html file is going to use AngularJS, the ng-app
directive must be specified. If specified on the html element, then the whole document will AngulaJs aware, if specified on a specific tag, then only that tag will be AngularJs aware.
ng-app
directiveng-app
directive per page<body ng-app="moduleName">
See how it works at https://www.w3schools.com/angular/
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
</head>
<body>
<div ng-app="">
<p>Client Details:</p>
<p>Firstname: <input type="text" ng-model="firstname" placeholder="First name"></p>
<p>Suname : <input type="text" ng-model="surname" placeholder="Surname"></p>
<h3>Hi {{firstname}} {{surname}} </h3>
</div>
</body>
</html>
Load the page in a browser and as you type in the first name and surname, the details are displayed in H3 tag while you type.
Client Details:
Firstname:
Suname :
Hi Sue Li
Angular uses double curly braces to bind expressions. It also posible to perform simple maths with number, e.g {{ (3 * 5) / 4 }}
Controllers build a model, which contains the data that we need to work with.
The ng-controller
directive is used to specify the name of the controller to be used on the page.
<div ng-app="">
<div ng-controller="MainController">
<p>Input something in the input box:</p>
</div>
</div>
ng-app
directive must be available on the page for ng-controller
to be recognised
A Javascript function MainController
must exist to support the controller specified in <div ng-controller="MainController">
var MainController = function ($scope) {
var person = {
firstname: "Lucas",
surname: "Don",
imgurl: "/img/icon.png"
}
$scope.message = "Hello from main controller";
$scope.person = person;
}
app.controller('MainController', ['$scope', '$log', function($scope, $log){
var person = {
firstname: "Lucas",
surname: "Don",
imgurl: "/img/icon.png"
}
$scope.message = "Hello from main controller";
$scope.person = person;
}]);
/** Why does it work?
* - A Javascript array accepts a function as data, [1, 2, function(){ console.log('Done!');}]
* - Functions even in arrays are annotated and minified
* - values are not minified ['$scope', '$log'] are treated as values. Variables are minified
* - the order of parameters is important when using this syntax
*/
Angular invokes Controller functions automatically when it needs to create a controler to manage an area of the web page.
Similar to the ng-
prefix, $
sign in parameter names (JS controller functions) signals that a Javascript function is in fact an Angular component.
Angular models are assigned to the controller parameter $scope
in above example. The $scope variable it's self is not the model, but the items attached to it become the model. In above example, message
is a variable that can now be attached to our html page.
NB: The term module is heavily overloaded in Javascript.
You create a module by creating a variable referencing angular.module('moduleName', [moduleDependencies])
function. The order of dependent modules is important. If controller names clash, the controller in the dependent module that was declared first takes precedence. See example below
// sample.js
// module app that depends on other modules, app2 and app3, all declared in the same file
angular.module('app', ['app2', 'app3']);
angular.module('app2', []);
angular.module('app3', []);
// create controller1 in module app3
angular.module('app3').controller('controller1', function($scope) {
$scope.name = 'controller1 in app3';
});
// create controller1 in module app2
angular.module('app2').controller('controller1', function($scope) {
$scope.name = 'controller1 in app2';
});
// the html
<body ng-app='app'>
<div ng-controller='controller1'> </div>
</body>
<!-- output
// angular.module('app', ['app2', 'app3']);
app2 is called first therefore "controller1 in app2" will be displayed
-->
As part of restricting variables declared on the Javascript global namespace, an Angular module can be declared to be the only global variable.
// myApp must be the only global variable for your Angular app
var myApp = angular.module("myApp", []);
myApp.controller('mainController', function($scope) {
$scope.message = 'Message from mainController';
});
The angular injector object contains a list of all objects within your application. Only 1 injector can exist in each angular application.
The reason controller1 in app2 is returned above has to do with the injector as the injector has registered the first available controller1 and there can only be one controller with that name in an injector.
There are 3 steps to working with modules. First two steps take place in Javascript, and the last on html pages
// Step 1. create a module called githubViewer
// Ommit brackets if you are referencing a module already defined, e.g third party module
var app = angular.module('githubViewer', []);
// Step 2. register your controller to the module
app.controller('MainController', MainController);
Tell Angular which module to use on html page
<!--
// Register module with Angular, on html page
// Angular will look for controllers in githubViewer module
-->
<body ng-app="githubViewer"> </body>
Module definition is contained in file app.js
, the controller is in main.js
and the html code in index.html
/**
* Angular module
*/
// declare module
var app = angular.module('githubViewer', []);
// register controllers here - ('NameOfController', FunctionToUse)
app.controller('MainController', MainController);
var MainController = function ($scope) {
var person = {
firstname: "Lucas",
surname: "Mangwana",
imgurl: "/img/icon.png"
}
$scope.message = "Hello";
$scope.person = person;
}
Page html code
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<script src="main.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="githubViewer">
<div ng-controller="MainController">
<p>Client details</p>
<p>Firstname: <input type="text" ng-model="person.firstname"> </p>
<p>Surname: <input type="text" ng-model="person.surname"> </p>
<p> {{message}} {{ person.firstname }} {{ person.surname }} </p>
</div>
</body>
</html>
Client details
Firstname:
Surname:
Hello Lucas Mangwana
$scope.firstname = 'Simba';
. Firstname is the model, and models can be functions too $scope.getUser(id)
{{ firstname }}
Sample form with ng-click. The search button has a function search(nickname)
that takes a nickname entered by user and searches an array of users. Displays the user's full name or "not found" message if there is no match.
<!-- index.html -->
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<script src="main.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="githubViewer">
<div ng-controller="MainController">
<form name="myForm">
<p>Client details</p>
<p>Nickname: <input type="text" ng-model="nickname" placeholder="Search nickname"> </p>
<p>Full Name: <input type="text" value="{{user.name || error.user}}" disabled> </p>
<p>Click to search for {{nickname}} <input type="button" value="Search" ng-click="search(nickname)"></p>
</form>
</div>
</body>
</html>
Project module code
// ./app.js
// declare module
var app = angular.module('githubViewer', []);
// register controllers here - ('NameOfController', FunctionToUse)
app.controller('MainController', MainController);
Code for MainController containing array of users and search function.
// ./main.js
var MainController = function ($scope) {
var person = {
firstname: "Lucas",
surname: "Mangwana",
imgurl: "/img/icon.png"
};
var users = [
{nickName: "sue", name: "Susan Boyle"},
{nickName: "pat", name: "Pat Shange"},
{nickName: "don", name: "The Donald"},
{nickName: "fau", name: "The Doctor"},
{nickName: "cov", name: "Self Isolation"}
];
$scope.search = function(nick) {
$scope.user = undefined;
$scope.error = {user: 'not found'};
if (typeof nick !== "string" || nick == "")
return false; // invalid search string
nick = nick.toLowerCase();
users.forEach(function(u) {
if (u.nickName.toLowerCase() == nick) {
$scope.user = u;
return true;
}
});
};
$scope.message = "Hello";
$scope.person = person;
}
Client details
Nickname:
Full Name:
Click to search for pat
The ngRoute module is stored in its own Javascript file which must be referenced
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular-route.js"></script>
To work with routes,
ngRoute
as a module dependencyng-view
directive to html pageEach route has an html view defined by a chuck of html and the ng-view
directive tells Angular where to place the chunk of html.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Angular Routes</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular-route.js"></script>
</head>
<body ng-app="app">
<div ng-view></div> <!-- view for each route will be displayed here -->
</body>
</html>
Most what we inject into controllers are services. Services are mostly singletons with the exception of $scope. Custom services that you create will be singletons.
// create a service, return the length of a fullName
// Services are singletons except $scope
app.service('nameService', function () {
var self = this;
this.fullName = 'Tim Shaw';
this.nameLength = function () {
return self.fullName.length;
}
};
// use nameService in mainController
// Inject the nameService into mainController
app.controller('mainController', function($scope, $log, nameService) {
$log.log(nameService.fullName); // Tim Shaw
$log.log(nameService.nameLength()); // 8
// set in scope
$scope.fullName = nameService.fullName;
// watch change in scope variable and update the variable in service
$scope.$watch('fullName', function() {
nameService.fullName = $scope.fullName;
});
});
// use nameService in secondController
// mainController and secondController now share data through nameService
app.controller('secondController', function($scope, $log, nameService) {
$log.log(nameService.fullName); // Tim Shaw
$log.log(nameService.nameLength()); // 8
// set in scope
$scope.fullName = nameService.fullName;
// watch change in scope variable and update the variable in service
// without the watch changes will not reflect in both controllers
$scope.$watch('fullName', function() {
nameService.fullName = $scope.fullName;
});
});
Views and controllers never interact directly.
Service used for making http calls (GET, POST, PUT, DELETE). Used to write / retrieve data from a database for example.
// Using http service
// $http parameter is required
var PersonController = function ($scope, $http) {
// get user data (get request). Don't do this. Use promises instead
$scope.user = $http.get("/user/100");
// using promises
$http.get("/user/100").then(
// on success callback
function(response){
$scope.user = response.data; //
},
// response failed callback
function (reason) {
$scope.error = "Record not found!";
}
);
}
Another example using $http module
// Using http service
// must inject $http module into the controller
// ... just showing alternative syntax
var myApp = angular.module('myApp', []);
myApp.controller('PersonController', ['$scope', '$http', '$filter' function($scope, $http, $filter) {
// showing filter use
$scope.handle = '';
$scope.lowercaseHandle = function () {
return $filter('lowercase')($scope.handle);
};
// get request
$http.get('/user/100')
.success(function (response){
$scope.user = response;
})
.error(function (err, status) {
$scope.error = err;
$scope.errorStatus = status;
});
// post request - write to database
$http.post('/user/100', {
firstname: $scope.firstname,
surname: $scope.surname,
age: $scope.age
})
.success(function (result) {
$scope.user = result; // send result back to page
})
.error(function (data, status) {
$scope.error = data;
});
}]);
// Angular module for noteApp
let noteApp = angular.module('noteApp', []);
// Custom service, store boards that can be shared by different controllers
// boardsService.getboards()
noteApp.factory('boardsService', function() {
let boards = [
{id: "01", title: "Title 1", content: "some text one"},
{id: "02", title: "Title 2", content: "some text two"},
{id: "03", title: "Title 3", content: "some text three"}
];
return {
getBoards : function() {
return boards;
}
};
});
// Controller using $scope, and boardsService
noteApp.controller('mainController', [
'$scope', '$filter', '$log', '$timeout', '$http', 'boardsService',
function($scope, $filter, $log, $timeout, $http, boardsService) {
$scope.boards = boardsService.getBoards();
$scope.firstname = '';
// Use ng-if/ng-show directive
// to check that firstname is greater than maxCharacters
$scope.minCharacters = 2;
// $filters: firstname to lowercase
// Format a given value to, "upercase, lowercase, currency, date, json, limitTo, OrderBy "
// https://docs.angularjs.org/api/ng/filter
$scope.lcaseFirstname = function () {
return $filter('lowercase')($scope.firstname);
};
// Digest loop: $watchers
// Watch for models ($scope values) changing
// $watchers keep track of newValue and oldValue for every model declared
$scope.$watch('firstname', function (newValue, oldValue){
$log.info('Firstname value changed');
$log.log('New value: ' + newValue);
$log.log('Old value: ' + oldValue);
});
// Digest loop: Outside AngularJs scope
// If changes are not made within the AngularJs context,
// the digest loop will not update everything as it should
// Code below will acknowledge that scope changed but will not update the DOM
// because the digest loop is never started
setTimeout(function () {
$scope.firstname = 'Lucas';
console.log('Scope changed 1');
}, 2000);
// Digest loop: Outside AngularJs scope
// Forcing digest loop, use $scope.$apply()
// The DOM will be updated to show firstname: Lucas
// Do this especially when you use external libraries like jQuery to manipulate the DOM
setTimeout(function () {
$scope.$apply(function () {
$scope.firstname = 'Lucas';
console.log('Scope changed 2');
});
}, 3000);
// Digest loop: Within AngularJs scope
// Alternatively you can inject and use AngularJs' $timeout service
// $scope.$apply() is not required within AngularJs scope
$timeout(function () {
$scope.firstname = 'Luke';
console.log('Scope changed 3');
}, 4000);
// Handler for ng-click directive
$scope.clickedMe = function() {
alert("I have been clicked!");
};
// $http service
// Use $http service to get data from API endpoint
$http.get('/api/students/get')
.success(function (result) {
$scope.rules = result;
})
.error(function (data, status) {
console.error(data);
})
//
// For use with $http.post()
// Add new student details from html form
$scope.newStudent = '';
$scope.addStudent = function () {
// $http service
// Post data to Api endpoint
$http.post('/api/students/save', { newStudent: $scope.newStudent })
.success(function (result) {
$scope.rules = result;
$scope.newStudent = '';
})
.error(function (data, status) {
console.error(data);
})
//
};
}
]);
// Custom directive
// Useful when you have repeating block of html as often seen on search results
// if replace is true, the directive is replace with html from template
// Directive names are normalised to with a '-' wherever a capital letter occurs
// 'searchResults' becomes 'search-results' on html page.
noteApp.directive('searchResults', function () {
return {
// force users to call directive only as 'A' attribute, 'E' Element, 'C' class, 'M' comment.
// 'AE' are turned on by default
// Turn on all forms of calling a directive on html
restrict: 'AECM',
// on html page, directive can be element , or attribute
// this will be replace by html defined on template variable
// use templateUrl: '/page/file.html' if template is in a separate html page.
template: '<a href="#" class="list-group-item"><h4>Custom Directive - search-results</h4><h5>Jane Logan</h5><div>1 Mulican Street, Hanover Way</div><small ng-transclude></small></a>',
// replace directive with html from template
replace: true,
// allow transclusion, is false by default
transclude: true,
// enforce isolated scope
scope: {
// @ represents a text data being passed to scope variable
// = represents two way binding
studentName: '@',
studentObject: '='
}
}
});
index.html
for app.js
above. Put the two files in the same folder to test
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.7.9/angular.min.js"></script>
<script src="app.js"></script>
<script src="//code.jquery.com/jquery-3.x-git.min.js"></script>
<script src="//cdn.usebootstrap.com/bootstrap/latest/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="//cdn.usebootstrap.com/bootstrap/latest/css/bootstrap.min.css">
</head>
<body ng-app='noteApp'>
<div ng-controller='mainController'>
<table class='table table-striped table-bordered table-hover'>
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Title</th>
<th scope="col">Description</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="board in boards">
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<div>
<label>
Student Name:
<input type="text" ng-model='firstname'>
</label>
<p ng-cloak>My name is </p>
<!-- Directives: ng-if -->
<!-- ng-if removes element from DOM when condition is false -->
<!-- Length of Firstname must be greater than $scope.minCharacters -->
<div class="alert alert-info" ng-if="firstname.length <= minCharacters">
ng-if: Firstname must be longer than characters
</div>
<!-- Directives: ng-show -->
<!-- ng-show sets display:none when condition becomes false -->
<div class="alert alert-info" ng-show="firstname.length <= minCharacters">
ng-show: Firstname is too short, must be more than characters
</div>
<!-- Directives: ng-show -->
<!-- ng-show sets display:none when condition becomes false -->
<div class="alert alert-info" ng-hide="firstname.length > minCharacters">
ng-hide: Firstname is too short, must be more than characters
</div>
<!-- Directives: ng-class -->
<!-- Set class alert-success if firstname greater than minCharacters -->
<div class="alert"ng-show="firstname.length !== minCharacters" ng-class="{'alert-success': firstname.length > minCharacters, 'alert-warning': firstname < minCharacters}">
<div ng-show="firstname.length <= minCharacters">
ng-class: First name is too short, must be characters long
</div>
<div ng-show="firstname.length > minCharacters">
ng-class: Firstname is valid
</div>
</div>
<!-- Directives: ng-click -->
<!-- Set a click handler -->
<div>
<label>ng-click Directive</label>
<button ng-click="clickedMe()">Click me!</button>
</div>
<!-- using $http.post() to post data to API endpoint -->
Add Student: <input type="text" ng-model="newStudent" />
<a href="#" class="btn btn-secondary" ng-click="addStudent()">Save</a>
<!-- // Custom directive called search-result -->
<!-- // Different ways of calling custom directive -->
<div search-results></div>
<div class='search-results'></div>
<search-results> * this text will be transcluded </search-results>
<!-- // repeat items in an array of objects -->
<div search-results ng-repeat="student in students"></div>
</div>
</div>
</body>
</html>