AngularJS

Single Page Application
- Single Web Page
- JS Heavy Client Application
- Server communication via AJAX (or WebSockets)
SPA
Why Angular?
- Fast prototyping & development
- Easy HTML extension
- Huge community & OS projects
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js">
</script>
</head>
<body>
<div>
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<hr>
<h1>Hello {{yourName}}!</h1>
</div>
</body>
</html>Hello Angular!
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js">
</script>
</head>
<body>
<div>
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<hr>
<h1>Hello {{yourName}}!</h1>
</div>
</body>
</html>Hello Anular!
ng-app
Declares application scope
ng-model
Declares model/variable binding (with input)
Directives
A declarative user interface
Angular build-in directives
ng-directive-name
{{ yourName }}
Databinding
+ watch & change
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<h1>Hello {{yourName}}!</h1>
More ng-stuff!
<!doctype html>
<html ng-app="todoApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js">
</script>
<script src="todo.js"></script>
<link rel="stylesheet" href="todo.css">
</head>
<body>
<h2>Todo</h2>
<div ng-controller="TodoListController">
<span>{{remaining()}} of {{todos.length}} remaining</span>
[ <a href="" ng-click="archive()">archive</a> ]
<ul class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>
<form ng-submit="addTodo()">
<input type="text" ng-model="todoText" size="30"
placeholder="add new todo here">
<input class="btn-primary" type="submit" value="add">
</form>
</div>
</body>
</html>More ng-stuff!
angular.module('todoApp', [])
.controller('TodoListController', function($scope) {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
$scope.addTodo = function() {
$scope.todos.push({text:$scope.todoText, done:false});
$scope.todoText = '';
};
$scope.remaining = function() {
var count = 0;
$scope.todos.forEach(function(todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.archive = function() {
var oldTodos = $scope.todos;
$scope.todos = [];
oldTodos.forEach(function(todo) {
if (!todo.done) $scope.todos.push(todo);
});
};
});TODO App (1)
<!doctype html>
<html ng-app="todoApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js">
</script>
<script src="todo.js"></script>
<link rel="stylesheet" href="todo.css">
</head>angular.module('todoApp', [])angular.module defines module: application = one or more modules
module name
module dependencies
TODO App (2)
<body>
<h2>Todo</h2>
<div ng-controller="TodoListController">
...
<ul class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>angular.module('todoApp', [])
.controller('TodoListController', function($scope) {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}
];
...
});
text binding span content
text binding class name
$scope
JS object
- give access to properties
- give access to functions
- "digest cycle"
- watch values
- detects changes
- applies dependent changes
TODO App (3)
<form ng-submit="addTodo()">
<input type="text" ng-model="todoText" size="30"
placeholder="add new todo here">
<input class="btn-primary" type="submit" value="add">
</form>angular.module('todoApp', [])
.controller('TodoListController', function($scope) {
...
$scope.addTodo = function() {
$scope.todos.push({text:$scope.todoText, done:false});
$scope.todoText = '';
};
...
});TODO App (4)
<div ng-controller="TodoListController">
<span>{{remaining()}} of {{todos.length}} remaining</span>
[ <a href="" ng-click="archive()">archive</a> ]angular.module('todoApp', [])
.controller('TodoListController', function($scope) {
...
$scope.remaining = function() {
var count = 0;
$scope.todos.forEach(function(todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.archive = function() {
var oldTodos = $scope.todos;
$scope.todos = [];
oldTodos.forEach(function(todo) {
if (!todo.done) $scope.todos.push(todo);
});
};
...
});Shopping Cart
Shopping Cart
<html ng-app='myApp'>
<body ng-controller='CartController'>
<h1>Your Order</h1>
<div ng-repeat='item in items'>
<span>{{item.title}}</span>
<input ng-model='item.quantity'>
<span>{{item.price | currency}}</span>
<span>{{item.price * item.quantity | currency}}</span>
<button ng-click="remove($index)">Remove</button>
</div>
<script src="angular.js"></script>
<script src="myapp.js"></script>
</body>
</html>angular.module('myApp', []).controller('CartController', function($scope){
$scope.items = [
{title: 'Paint pots', quantity: 8, price: 3.95},
{title: 'Polka dots', quantity: 17, price: 12.95},
{title: 'Pebbles', quantity: 5, price: 6.95}
];
$scope.remove = function(index) {
$scope.items.splice(index, 1);
}
});More Theory
- Commonly used features
- Communication with Server
- Directives
Templates & Data Binding
<p>{{greeting}}</p>
<p ng-bind="greeting"></p>
$scope.greeting = "Hello there!"
---
<p>Hello there!</p>
<p>Hello {{name}}!</p>
<p ng-bind-template="Hello {{name}}!"></p>
$scope.name = "Ned Stark"
---
<p>Hello Ned Stark!</p>
While loading application:
- {{zxc}} => {{zxc}}
- ng-bind =>
- Hi {{zxc}} => Hi {{zxc}}
- ng-bind-template =>
- {{}} expression changes only interior
- ng-bind changes content of tag
Templates & Data Binding
<p ng-cloak>Hello {{name}}!</p>
<p ng-cloak>Hello {{name}} from {{city}}!</p>
- ng-cloak - hides content while loading
- CSS:
- after compiling, "ng-cloak" attribute is removed
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}Templates & Databinding
<p>{{count * price() | currency}}</p>
<p ng-bind="count * price() | currency"></p>
<p>Hello {{name}} from {{city}}!</p>
<p>
Hello
<span ng-bind="name"></span>
from
<span ng-bind="city"></span>
!
</p>
<p ng-bind-template="Hello {{name}} from {{city}}!">
</p>- {{}} - expression
- ng-bind - expression
- ng-bind - only one expression
- ng-bind-template - multiple expressions
Templates & Databinding
<a href="{{user.profile.url}}">
Link to user profile
</a>
<img src="{{user.avatar.url}}">
<a ng-href="user.profile.url">
Link to user profile
</a>
<img ng-src="user.avatar.url">- src/href + databinding = broken image/link
- instead use ng-src/ng-href
- databinding works with class atribute
- complex dynamic classes - ng-class
- ng-class evaluates values for truthy/falsy
<form class="form form-{{form.state}}">
...
</form>
<ul>
<li ng-class="{'active': state == 'home'}">
Home
</li>
<li ng-class="{'active': state == 'settings'}>
Settings
</li>
</ul>Controllers & scopes
<div ng-controller="MyCtrl">
<!-- Here we have access to MyCtrl scope -->
</div><div ng-controller="MyCtrl">
<!-- Here we have access to MyCtrl scope -->
<div ng-controller="InnerCtrl">
<!-- Here we have access to InnerCtrl scope -->
</div>
</div>- controller - creates new $scope and allows to control it
- controllers can be nested
- new $scope is child of parent scope
- top level scope is $rootScope created for angular application
<body ng-app>
<!-- Here we have access to app's rootScope -->
<div ng-controller="MyCtrl">
<!-- Here we have access to MyCtrl scope -->
</div>
</body>Controllers & scopes
<div ng-controller="MyCtrl">
<p>Hello {{name}}!</p>
<p>You live in: {{place}}</p>
<p>Your job is: {{job}}</p>
<div ng-controller="InnerCtrl">
<p>Your name is: {{name}}</p>
<p>You live in: {{place}}</p>
<p>Your job is: {{job}}</p>
</div>
</div>
app.controller('MyCtrl', function($scope){
$scope.name = "Cercei";
$scope.place = "King's Landing";
});
app.controller('InnerCtrl', function($scope){
$scope.name = "Joffrey";
$scope.job = "King";
$scope.$parent.job = "Queen";
});- new $scope inherits parent $scope prototype
- inner $scope overshadows parent $scope properties
- when property not found in $scope, it's looked up in parent $scope
- parent has no direct access to child $scope
- child can access parent scope by $scope.$parent
Scopes & watches
<div ng-controller="MyCtrl">
<p>Hello {{name}}!</p>
<p>Name length: {{length}}</p>
<p>It's a {{message}} name!</p>
<input type="text" ng-model="name">
</div>
app.controller('MyCtrl', function($scope){
$scope.name = "Hodor";
$scope.length = $scope.name.length;
$scope.message = "short";
$scope.$watch('name', function(val){
$scope.length = val.length;
})
$scope.$watch('length', function(val){
$scope.message =
(val > 20) ? 'long' : 'short';
});
});- each 'live' databinding use watch internally
- controllers allow watch scope properties
- all watches are checked and applied during
Digest Cycle - beware watch loops - Digest Cycle stops after 10 iterations
- outside of digest loop use $scope.$apply(function)
Conditional interface
<div>
<ul ng-show="user.interests">Interests:
<li ng-repeat="interest in user.interests">
{{interest}}
</li>
</ul>
<p ng-hide="user.interests">
No interests selected!
</p>
</div>
<div>
<video ng-if="intro.active" src="tutorial.ogg">
</video>
</div>- to show or hide part of UI use ng-show/ng-hide
- it applies CSS display: none!important
- ng-if actually removes and recreates DOM nodes
Conditional interface
<div>
<ul ng-show="user.interests">Interests:
<li ng-repeat="interest in user.interests">
{{interest}}
</li>
</ul>
<p ng-hide="user.interests">
No interests selected!
</p>
</div>
<div>
<video ng-if="intro.active" src="tutorial.ogg">
</video>
</div>
<div ng-switch on="user.category">
<p>Your prize is:</p>
<b ng-switch-when="child">bag of candies!</b>
<b ng-switch-when="adult">bottle of beer!</b>
<b ng-switch-default>10$</b>
</div>- to show or hide part of UI use ng-show/ng-hide
- it applies CSS display: none!important
- ng-if actually removes and recreates DOM nodes
- ng-switch generates DOM content based by choice
Operations within interface
<div ng-include="sidebar.html"></div>- ng-include loads template via AJAX
- ng-init creates or sets value within scope
- ng-click executes commands
- ng-click can create or set value (like ng-init)
- ng-click can run function
<div ng-init="votes = 0">
<button ng-click="active = true">
Activate
</button>
<button ng-click="votes = votes + 1">
Vote
</button>
<button ng-click="activate()">
Activate
</button>
</div>Collections
- ng-repeat iterate over collection
- copy of template for each item
- separate scope for each item
- special properties for local scope:
- $index (0 .. length -1)
- $first / $last (bool)
- $middle (bool)
- $odd / $even (bool)
<table>
<tr ng-repeat="person in people">
<td ng-class="{'row-odd': $odd}">Entry</td>
<td>{{person.name}}</td>
<td>{{person.city}}</td>
<td>{{person.age}}</td>
<td>
<button ng-click="notice($index)">
Send notice
</button>
</td>
</tr>
</table>Collections
- ng-repeat easily combines with filters:
- filter - query matching
- orderBy - choose property to order items
<div ng-controller="MyCtrl">
<form class="form-inline">
<input ng-model="query" type="text"
placeholder="Filter by" autofocus>
</form>
<ul ng-repeat="friend in friends | filter:query | orderBy: 'name' ">
<li>{{friend.name}}</li>
</ul>
</div>Forms
ng-submit cancel default browser submit action and launch provided function for handling form processing
<form action="/user/profile" method="POST">
<input type="text" name="nick">
<textarea name="comment"></textarea>
<input type="submit value="Send">
</form>
<form ng-submit="postComment()">
<input type="text" ng-model="post.nick">
<textarea ng-model="post.comment"></textarea>
<button>Send</button>
</form>
app.controller('CommentCtrl', function($scope){
$scope.postComment = function(){
sendToServer($scope.post);
}
});Form inputs
ng-model works with most types of inputs:
text, email, password, radio, checkbox, textarea
<form>
Name:
<input type="text" ng-model="user.name" required /><br />
E-mail:
<input type="email" ng-model="user.email" /><br />
Gender:
<input type="radio" ng-model="user.gender" value="male" />
male
<input type="radio" ng-model="user.gender" value="female" />
female<br />
<textarea ng-model="user.description"></textarea>
<input type="submit" ng-click="update(user)" value="Save" />
</form>Form inputs
ng-model works also with select
ng-repeat can be used for dynamic options list
<form>
<select name="singleSelect" ng-model="data.singleSelect">
<option value="option-1">Option 1</option>
<option value="option-2">Option 2</option>
</select>
<br>
<select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
<option ng-repeat="option in data.availableOptions" value="{{option.id}}">
{{option.name}}
</option>
</select>
<input type="submit" ng-click="update(user)" value="Save" />
</form>Form validation
ng-model adds these classes:
- ng-valid: the model is valid
- ng-invalid: the model is invalid
- 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
<form novalidate>
Name:
<input type="text" ng-model="user.name" required>
E-mail:
<input type="email" ng-model="user.email" required>
<input type="submit" ng-click="save()" value="Save">
</form>Form object
validation adds these properties:
- formObject.$valid: form is valid
- formObject.$invalid: form is invalid
- formObject.$pristine: form hasn't been interacted with yet
- formObject.$dirty: the control has been interacted with
- formObject.$submitted: form has been submitted
<form name="userForm" ng-submit="saveUser()" novalidate>
Name:
<input type="text" ng-model="user.name" required>
E-mail:
<input type="email" ng-model="user.email" required>
<input type="submit" ng-disabled="userForm.$invalid" value="Save">
</form>Form field validation
validation adds these properties:
- formObject.email.$valid: field is valid
- formObject.email.$invalid: field is invalid
- formObject.email.$pristine: field hasn't been interacted with yet
- formObject.email.$dirty: field has been interacted with
- formObject.email.$touched: field has been blurred
- formObject.email.$untouched: field hasn't been blurred
<form name="userForm" ng-submit="saveUser()" novalidate>
Name:
<input type="text" ng-model="user.name" name="name" required>
E-mail:
<input type="email" ng-model="user.email" name="email" required>
<input type="submit" ng-disabled="userForm.$invalid" value="Save">
</form>Form validators
basic validators:
- required / ng-required
- ng-minlength - min length of string
- ng-maxlength - max length of string
- ng-pattern - RegExp validation
- ng-trim allows to preserve leading/trailing whitespaces
- ng-change - calls expression any time model changes
<input
ng-model="string"
[name="string"]
[required="string"]
[ng-required="boolean"]
[ng-minlength="number"]
[ng-maxlength="number"]
[ng-pattern="string"]
[ng-change="string"]
[ng-trim="boolean"]>
...
</input>Form errors
<div class="form-group">
<label>Name</label>
<input type="text" name="name" class="form-control" ng-model="name" required>
<p ng-show="userForm.name.$invalid && userForm.name.$dirty" class="help-block">
You name is required.</p>
</div>
<div class="form-group">
<label>Username</label>
<input type="text" name="username" class="form-control"
ng-model="user.username" ng-minlength="3" ng-maxlength="8">
<p ng-show="userForm.username.$error.minlength" class="help-block">Username is too short.</p>
<p ng-show="userForm.username.$error.maxlength" class="help-block">Username is too long.</p>
</div>objectForm.model.$error:
-
object containing all model errors
-
key - validator, value - error message
Routing
<html ng-app="admin">
<head>
<script src="src/angular.js">
</script>
<script src="src/app.js"></script>
</head>
<body>
<h1>Admin panel</h1>
<div ng-view></div>
</body>
</html>ng-view - directive loading and switching views based on route
$routeProvider - service user for defining routes
$routeParams - service for retrieving set of route parameters
var app = angular.module('admin', []);
app.config(function($routeProvider){
$routeProvider
.when('/', {
controller: 'DashboardCtrl',
templateUrl: 'users/dashboard.html'
})
.when('/user/:id', {
controller: 'UserCtrl',
templateUrl: 'users/profile.html',
})
.otherwise({redirectTo: '/'});
});
app.controller('DashboardCtrl', function($scope){
$scope.users = gelAllUsers();
});
app.controller(
'UserCtrl', function($scope, $routeParams){
$scope.user = getUser($routeParams.id);
});
Services, DI & modules
Modules - since JS have no modules system (almost true), Angular implements it's own system
Dependency Injection - it's mechanism used when dependency is required for module - it prepares and provides dependency for module
Services - modules that contains logic unrelated to views (reusable logic outside of controllers and directives)
app.config(function($routeProvider){
...
});
app.controller(
'DashboardCtrl', function($scope){
...
});
app.controller(
'UserCtrl', function($scope, $routeParams){
...
});
Built-in services
- $rootScope - root scope access
- $location - URL API
- $http - HTTP server comunication (AJAX)
- $q - promises (sane asynchronous workflows)
- $sce - Strict Contextual Escaping
- $document - digest cycle aware document
- $window - digest cycle aware window
- $timeout - digest cycle aware timeouts
- $routeProvider - routing definitions
- ...
Custom services
- factory() - function that returns one instance (singletion)
- service() - creates new instance from constructor
- constant() - holds value (can used in config)
- value() - holds value (variable, object, function)
- provider() - object with $get method, used internally to build factories and services, can be used in config
Custom service
app.factory('Users', function() {
function create(user) { ... }
function getAll() { ... }
function find(id) { ... }
function delete(id) { ... }
return {
create: create,
getAll: getAll,
find: find,
delete: delete
}
});
app.controller('MyCtrl', function($scope, Users){
$scope.usersList = Users.getAll();
$scope.currentUser = Users.find(1);
})app.factory('github', function() {
var serviceInstance = {};
// define Github API calls
return serviceInstance;
});
app.controller('MyCtrl',
function($scope, Users, github){
$scope.usersList = Users.getAll();
$scope.currentUser = Users.find(1);
$scope.projects =
github.getProjects(currentUser.email);
})Custom service
app.factory('CurrentUser', function() {
function isAuthorised() { ... }
function getCurrentUser() { ... }
function authorize() { ... }
function isAdmin() { ... }
function logout() { ... }
return {
isAuthorised: isAuthorised,
getCurrentUser: getCurrentUSer,
authorize: authorize,
isAdmin: isAdmin,
logout: logout
}
});
app.factory('audio', function($document) {
var audio = $document[0].createElement('audio');
return audio;
});
app.factory('player', function(audio) {
var player = {
playing: false,
current: null,
ready: false,
play: function(program) {
...
},
stop: function() {
...
}
};
return player;
});AJAX
Asynchronous JavaScript and XML
Standard flow
- URL input / link click
- browser makes HTTP GET request
- server sends back HTML + asets
- browser loads and render new page
- Sending form
- browser makes HTTP POST request (form data included)
- server receives data, process and sends back new page
- browser loads and render new page
AJAX
Asynchronous JavaScript and XML
AJAX flow
- user or client action
- create XMLHttpRequest
- set request endpoint (URL)
- set request type (GET, POST, PUT, DELETE)
- add data if needed
- define callback (function to process results)
- run request
- server receives request, process it and sends back respond
- browser detects respond and fire callback
- create XMLHttpRequest
REST(ful) API
Representational State Transfer (REST)
GET
Read a specific resource (by an identifier) or a collection of resources.
PUT
Update a specific resource (by an identifier) or a collection of resources.
DELETE
Remove/delete a specific resource by an identifier.
POST
Create a new resource. Also a catch-all verb for operations that don't fit into the other categories.
/users/12345
- collection: users
- item id: 12345
/customers/33245
/user/12/comments
/orders/8769/lineitems/1
Asynchronous JS
Callbacks
function doNumbers(x, y, callback) {
var my_number =
Math.ceil(Math.random() * (x - y) + y);
callback(my_number);
}
doNumbers(5, 15, function(num) {
console.log("callback called! " + num);
});
//------------------------------------
function handleData(data) {
alert(data);
}
$.ajax({
url : 'example.com',
type: 'GET',
success : handleData
});
button.on('click' function(){
console.log('button is clicked!');
})- useful for non-blocking operations
- used for complex data flow and events handling
- hard to handle in large applications
- "callback hell"
Asynchronous JS
Promises
var promise = getData()
.then(function(string) {
console.log(string)
}, function(error) {
console.error(error)
})
.finally(function() {
console.log('Finished at:', new Date())
});
var promise = getData()
.then(function(num) {
console.log(num)
return num * 2
})
.then(function(num) {
console.log(num) // = random number * 2
})- useful for non-blocking operations
- easy success/error handling
- allows for async chaining
- no more "callback hell"
- magic inside!
Server communication
$http
var promise = $http({
method: 'GET',
url: '/api/users.json'
});
promise.then(function(resp) {
// do sth with users
});
promise.success(function(...));
promise.error(function(...));
$http.get('api/user', {params: {id: '5'}})
.success(function(data, status, headers, config) {
// Do something successful.
}).error(function(data, status, headers, config) {
// Handle the error
});
var postData = {text: 'long blob of text'};
$http.post('api/user', postData, {params: {id: '5'}})
.success(function(data, status, headers, config) {
// Do something successful
}).error(function(data, status, headers, config) {
// Handle the error
});
app.controller('MyCtrl',
function($scope, $http){
$scope.users = [];
$http.get('api/user')
.success(function(data) {
$scope.users = data.users;
}).error(function() {
console.log('Cannot load users!');
});
})Server communication
$http
app.factory('githubService', function($http) {
var githubUsername;
var doRequest = function(path) {
return $http({
method: 'JSONP',
url: 'https://api.github.com/users/' + githubUsername + '/' + path + '?callback=JSON_CALLBACK'
});
}
return {
events: function() { return doRequest('events'); },
setUsername: function(newUsername) { githubUsername = newUsername; }
};
});Server communication
$http
app.factory('Findings', function($http) {
var base;
base = "/api/v1/findings";
return {
index: function(params) {
return $http.get(base + ".json", {
params: params
});
},
"new": function(params) {
return $http.post(base + ".json", params);
},
update: function(id) {
return $http.put(base + "/" + id + ".json");
},
single: function(id) {
return $http.get(base + "/" + id + ".json");
},
remove: function(id) {
return $http["delete"](base + "/" + id + ".json");
},
title: function(link) {
return $http.get(base + "/get_title.json", {
params: {
link: link
}
});
}
};
});app.controller('findingCtrl',
function($scope, $routeParams, Findings, Comments) {
$scope.id = $routeParams.finding_id;
Findings.single($scope.id).success(function(data) {
return $scope.finding = data;
});
$scope.saveComment = function(id, type, content) {
if (content != null) {
Comments["new"](id, type, content).then(function() {
Findings.single($scope.id).success(function(data) {
$scope.finding = data;
});
$scope.content = '';
});
}
};
});
Directives
How to use directive?
- A new HTML element (<date-picker></date-picker>).
- An attribute on an element (<input type="text" date-picker/>).
- As a class (<input type="text" class="date-picker"/>).
- As comment (<!--directive:date-picker-->).
Best practices: element or attribute
Directives
app.directive('helloWorld', function() {
return {
restrict: 'AE',
replace: 'true',
template: '<h3>Hello World!!</h3>'
};
});<div>
<hello-world/>
</div>
<div>
<div hello-world></div>
</div>
-------------------------
<div>
<h3>Hello World!</h3>
</div>app.directive('helloWorld', function() {
return {
restrict: 'AE',
template: '<h3>Hello World!!</h3>'
};
});<div>
<hello-world/>
</div>
<div>
<hello-world>
<h3>Hello World!</h3>
</hello-world>
</div>
---------------------
<div>
<div hello-world></div>
</div>
<div>
<div hello-world>
<h3>Hello World!</h3>
</div>
</div>Directives
app.directive('helloWorld', function() {
return {
restrict: 'AE',
template: '<h3>{{greeting}} {{name}}{{shout()}}</h3>',
link: function(scope, element, attr) {
scope.greeting = "Hi";
scope.shout = function() {
return "!".repeat(scope.name.length);
}
}
};
});Link function gives access to:
- child scope (inherited from parent scope)
- DOM element where directive is applied
- array of normalized attributes of element
Directives
Why using parent scope would be a bad idea?
- code is dependent on parent scope
- code can conflict with context
- code is not reusable
How to solve this problem?
- don't expect the exact context
- communicate via interface
- isolate inner scope
Directives
app.directive('helloWorld', function() {
return {
restrict: 'AE',
scope: {
greeting: '@greeting', // in this case '@'
name: '=name' },
template: '<h3>{{greeting}} {{name}}{{shout()}}</h3>',
link: function(scope, element, attr) {
//scope.greeting = "Hi";
scope.shout = function() {
return "!".repeat(scope.name.length);
}
}
};
});
<div hello-world greeting="Hi"
name="user.name"></div>
<div hello-world greeting="{{user.greeting}}"
name="user.name"></div>Passing variables to scope:
- use scope object definition
- each property is declared in attributes
- each property has type of binding
- '@' one way text binding
- '=' two way binding
- '&' function/expression callback
Directives
app.directive('helloWorld', function() {
return {
restrict: 'AE',
scope: {
greeting: '@greeting', // in this case '@'
name: '=name' },
template: '<h3>{{greeting}} {{name}}{{shout()}}</h3>',
link: function(scope, element, attr) {
//scope.greeting = "Hi";
scope.shout = function() {
return "!".repeat(scope.name.length);
}
}
};
});
<div hello-world greeting="Hi"
name="user.name"></div>
<div hello-world greeting="{{user.greeting}}"
name="user.name"></div>Passing variables to scope:
- use scope object definition
- each property is declared in attributes
- each property has type of binding
- '@' one way text binding
- '=' two way binding
- '&' function/expression callback
Isolated scope
- directive has no access to parent scope
- parent controller has no access to directive scope
- can be hard at times, but leads to nice modularity
When to use directives
- if you need to manipulate DOM
- if you need to hack HTML
- if you need to extend HTML
- if you build interface components
- if you integrate UI plugins with Angular
Good luck!
PS Workshops!
AngularJS Intro
By Krzysztof Jung
AngularJS Intro
- 1,380