2. History
Developed in 2009, Misko Hevery Google, Adam Abrons at Brat Tech LLC
Abrons left enter Igor Minar, Vojita Jina
Google Web Toolkit was too damn slow to work with. Enter GetAngular
Sponsored by Google
Current version 1.4.1 , The are working on angular 2.0 as well using ecmascript 6
3. Your first app
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body ng-app>
{{ 2+2 }}
<script src="angular.js"></script>
</body>
</html>
ng-app
Creates an application context
5. Add a controller
<body ng-app="app">
<div ng-controller="personController">
<h1>{{ title }}</h1>
<fieldset>
{{ person.name }}
{{ person.address }}
</fieldset>
</div>
<script src="angular.js"></script>
</body>
angular
.module('app', [])
.controller('personController', function ($scope) {
$scope.title = "A person view";
$scope.person = {
name : 'John Doe',
address : 'Unknown'
}
});
Controller context
6. Interacting - the view
ng-click, calls a callback on your scope
ng-repeat loops out an array
on your scope
ng-model, creates a two-way binding
between view and controller
<body ng-app="app">
<div ng-controller="personController">
<h1>{{ title }}</h1>
<fieldset>
<p>
<input type="text" ng-model="person.name" placeholder="name" />
</p>
<p>
<input type="text" ng-model="person.address" placeholder="address" />
</p>
<p>
<input type="text" ng-model="person.age" />
</p>
</fieldset>
<button ng-click="save()">Save</button>
<ul ng-show="errors.length > 0">
<li ng-repeat="error in errors">
{{ error }}
</li>
</ul>
</div>
<script src="angular.js"></script>
ng-show, boolean expression that
shows or hides your element
7. Interacting – changing data
<fieldset>
<p>
<input type="text" ng-model="person.name" placeholder="name" />
</p>
<p>
<input type="text" ng-model="person.address" placeholder="address" />
</p>
<p>
<input type="text" ng-model="person.age" />
</p>
</fieldset>
angular
.module('app', [])
.controller('personController', function ($scope) {
$scope.title = "A person view";
$scope.person = {
name : 'John Doe',
address : 'Unknown',
age : 0
}
Change this
Change reflected here
8. Interacting – saving…
<button ng-click="save()">Save</button> angular
.module('app', [])
.controller('personController', function ($scope) {
$scope.title = "A person view";
$scope.person = {
name : 'John Doe',
address : 'Unknown',
age : 0
}
$scope.save = function (){
if (getErrors().length === 0) {
console.log('send data to backend');
} else {
console.log('has validation errors');
}
9. Interacting - boolean + loop
<ul ng-show="errors.length > 0">
<li ng-repeat="error in errors">
{{ error }}
</li>
</ul>
function getErrors(){
var errors = [];
if (person.name === '') {
errors.push('first name missing');
}
if (person.lastname === '') {
errors.push('last name missing');
}
if (person.age < 18) {
errors.push('must be 18 or over');
}
return errors;
}
$scope.errors = [];
}
10. What else can a module do
Factory
Service
Provider
Filter
Config – you wire up routing etc here..
Value - happens after config
Constant - happens first
11. Dependency injection
angular
.module('app')
.controller('ctrl', function($scope, personService, Product, constantValue){
})
Type the name of it and angular will look in its core for a definition and
Inject it
angular
.module('app')
.controller('ctrl',[
'$scope',
'personService',
'Product',
'constantValue',
function($scope, personService, Product, constantValue){
// do stuff
}])
Minification safe
12. Factory – acting as a service
- The factory is a method on the module
- Its a singleton
- You should return something from it
angular
.module('app', [])
.factory('mathFactory', function () {
return {
add : function (lhs, rhs) {
return lhs+ rhs
}
}
}) Returning an object literal
angular
.module('app', [])
.controller('personController', function ($scope, mathFactory) {
mathFactory.add(2,2) // outputs 4
}
13. Factory – acting as model factory
angular
.module('app', [])
.factory('User', function () {
function User(dto){
this.firstName = dto.firstname;
this.lastName = dto.lastname;
this.age = dto.age;
}
User.prototype.getFullName = function() {
this.firstname + " " + this.lastname;
}
User.prototype.canVote = function (){
return this.age > 18;
}
return User;
})
Constructor method angular
.module('app', [])
.factory('userService', function ($http, User) {
var getData = function (){
// get users from backend
var usersFromBackend = [];
var users = [];
usersFromBackend.forEach(function (userDto) {
users.push(new User(usersDto));
});
return users;
}
return {
getUsers : getData
};
});
14. Service
angular
.module('app')
.service('dateService', function () {
this.getHolidays = function () {
// code
};
this.getWorkingDays = function () {
// code
};
})
angular
.module('app')
.controller('anyController', function ($scope, dateService) {
$scope.dates = dateService.getHolidays();
});
15. Filter – formatting
1) Format data
$scope.val = 1140,123567
{{ val | number : 0 }}
// 1,140 thousand separator
{{ val | number : 4 }}
// 1,140.1236 rounded up and 4 decimals
1b) Format data – custom filter
angular
.module('app')
.filter('prefixed', function () {
return function (val) {
return "#" + val;
}
})
Usage
$scope.prefixThis = ’hello’;
<p>
{{ prefixThis | prefixed }}
</p>
// ’#hello’
16. Filtering
2) Select a subset from a list – non specific
{ { filter_expression | filter : expression : comparator} }
<input type="text" ng-model="filterByName" />
<div ng-repeat="user in users | filter:'filterByName'">
{{ user.name }}
</div>
<!-- will only look at 'name' -->
<input type="text" ng-model="search.name" />
<table >
<tr><th>Name</th><th>Phone</th></tr>
<tr ng-repeat="friendObj in friends | filter:search:strict">
<td>{{friendObj.name}}</td>
<td>{{friendObj.phone}}</td>
</tr>
</table>
2b) Select a subset from a list –specific
2c) orderBy filter
<div ng-repeat="item in items | orderBy: '+name' ">
{{item}}
</div>
<div ng-repeat="item in items | orderBy: scopeProperty ">
{{item}}
</div>
Filter by item.name
Filter by a property on the scope
+- sort order
24. Backend promise – resolve / reject
$q is used to create promises and also to resolve/ reject promises
function getData(value){
var deferred = $q.defer();
if (value > 5) {
deferred.resolve("higher than five");
} else {
deferred.reject("boo too low");
}
return deferred.promise;
}
function callData(){
getData(6).then(function (result) {
console.log(result); // returns 'higher than five'
});
getData(1).then(function (result) {
// never comes here
}, function (error) {
console.log(error); // returns 'boo too low'
});
}
25. Backend promise, wait for all
function longRunningService() {
var deferred = $q.defer();
$timeout(function () {
deferred.resolve('long');
}, 4000);
return deferred.promise;
}
function shortRunningService() {
var deferred = $q.defer();
$timeout(function () {
deferred.resolve('short');
}, 1000);
return deferred.promise;
}
function call() {
return $q.all([shortRunningService(), longRunningService()]).then(results) {
// 4 seconds later results[0] and results[1] populated
} ;
}
26. Backend promise, hiearchical call
function validateRequestIsAuthenticated() {
var deferred = $q.defer();
deferred.resolve('a');
return deferred.promise;
}
function validateParameters() {
var deferred = $q.defer();
deferred.resolve('b');
return deferred.promise;
}
function loginUser() {
var deferred = $q.defer();
deferred.resolve('c');
return deferred.promise;
}
function getProducts() {
var deferred = $q.defer();
deferred.resolve('d');
return deferred.promise;
}
function call() {
return validateRequestIsAuthenticated()
.then(getCustomer().then(function(customer){
getProductsByCustomer(customer.id)
}))
.then(loginUser)
.then(getProducts)
.then(function (products) {
return products;
}, function (error) {
// something failed with one of our calls
});
}
One place to handle
error from any of the
above
Calls in order
27. Interceptors
http interceptors can be applied to request and response and on success and error respectively
So when is a good time to use it?
- Handle all incoming error responses, route to error page
- All outgoing requests should have a custom header
And other things you can think of that should happen on a global level for all requests / responses
- Read data from cache if application seems to be offline
28. Interceptor – set a custom header on all
requests
.factory('authService', function() {
return {
isLoggedIn : function () {
return true;
},
token : 'aToken'
};
})
.factory('customHeaderInjector', function(authService) {
return {
request : function (config) {
if (authService.isLoggedIn()) {
config.headers['Authentication'] = authService.token;
}
return config;
}
}
})
30. Watch
When a scope property has change and you want to know about it
$scope.personName = 'Zlatan';
$scope.save = function(val){
$scope.personName = val;
}
$scope.$watch('personName', function(newValue, oldValue){
});
$scope.person = {
name : 'Zlatan',
age : 33
};
$scope.save = function(val){
$scope.person = val;
}
$scope.$watch('person', function(newValue, oldValue){
},true);
True, look at the whole object hierarchy
32. Events – two controllers talking
parent => child $scope.$broadcast(’kids – sit still in the car’, data)
child => parent $scope.$emit(’mommy – are we there yet’,data)
sibling => sibling $rootScope.$emit(’event’, data) listeners are other $rootScope.$on(’event’)
Madman $rootScope.$broadcast(’telling the whole world – the end is near, repent’,data)
Best!
33. Events – code example
.factory('eventService', function($rootScope) {
return {
publish : function (eventName, data) {
$rootScope.$emit(eventName, data);
},
subscribe : function (eventName, callback) {
$rootScope.$on(eventName, callback);
}
}
})
.controller('appController', function($scope, eventService){
$scope.send = function(){
eventService.publish('app_event',{ data : 'data from app controller' });
};
.controller('firstController', function($scope, eventService){
eventService.subscribe('app_event', function(event, data){
$scope.appMessage = data.data;
})
Publish to whoever listens to that eventHelper
Keep track on namespaces for events and be careful, don’t spam
36. Directives - intro
The main idea is to
- clean up html
and also to
- create reusable parts aka ”user controls”.
Directives can be applied on different levels
- as its own element
- as an attribute on an existing element, also called decorating directive
- on css level
- on a comment
I will be covering the two first ones Element + Attribute
37. Directives – most concepts
angular
.module('app')
.directive('someDirective', function () {
return {
restrict : 'E',
// E= element,A = attribute,C= css class, M = comment, what this directive can be applied to
replace : true, // whether to let template replace element tag or keep element tag
scope : true, // true = new scope that inherits, false = parent scope, {} = new + no inheritance
template : ’<h1>{{title}}</h1>', // or templateUrl
controller : function ($scope) { },
link : function (scope, element, attributes) { }
}
39. Directive - Info card
angular
.module('app')
.directive('infoCard', function () {
return {
restrict : 'E',
scope : false,
replace : true,
template : '<div>'+
'<h1>{ { title }}</h1>'+
'<p><input type="text" ng-model="info" /></p>'+
'</div>'
}
})
You can do this, but
Change one, change all!!
Probably not what you wanted
<div ng-app="app">
<div ng-controller="appController">
<info-card></info-card>
<info-card></info-card>
<info-card></info-card>
<div ng-view></div>
</div>
</div>
angular
.module('app')
.controller('appController', function ($scope) {
$scope.title = 'My Application';
$scope.content = '';
})
Content is from controller so it is shared
between all instances
40. Directive – a better info card
angular
.module('app')
.directive('infoCardImproved', function () {
return {
replace : true,
restrict : 'E',
template : '<div class="info-card-improved">' +
'<h2>{{title}}</h2>' +
'<p><input type="text" ng-model="text" /></p>'
+ '</div>',
scope : true,
controller : function ($scope) {
$scope.text = 'empty text';
$scope.title = 'infoCardImproved';
}
}
})
<div ng-app="app">
<div ng-controller="appController">
<info-card-improved></info-card-improved>
<info-card-improved></info-card-improved>
<info-card-improved></info-card-improved>
<div ng-view></div>
</div>
</div>
This works as intended
41. Directive , isolated scope
angular
.module('app')
.directive('isolatedDirectiveValue', function () {
return {
restrict : 'E',
replace : true,
scope : {
title : '@',
description : '@'
},
template : '<div><h2>{{title}}</h2><p>{{description}}</p></div>',
controller : function ($scope) {
}
}
});
The scope is isloated in that it points to an object
scope : {}
Instead of false/true
42. Directive, isolated binding types
An isolated scope has its own scope but it can also communicate with data being binded to it
scope : {
title : '@',
description : '@'
}
Binding to static value <directive-name title="a value" description="a description value" ></directive-name>
Binding to a scope property
scope : {
title : '=',
description : '=’
}
<directive-name title="scopeProperty" description="scopePropertyDesc"></directive-name>
scope : {
updated : ’&',
}
Binding to a scope callback <directive-name changed="onUpdatedCallback" ></directive-name>
43. Directive, isolated callback
angular
.module('app')
.directive('dayBrowser', function () {
return {
replace : true,
restrict : 'E',
scope : {
dayChanged : '&'
},
templateUrl : 'directives/dayBrowser/dayBrowser.html',
link : function (scope, element, attrs) {
scope.day = new Date();
function addDays(currentDate, days) {
var newDate = new Date(currentDate);
newDate.setDate(currentDate.getDate() + days);
return newDate;
} ;
scope.incrementDate = function (val) {
scope.day = addDays(scope.day, val);
scope.dayChanged({ date : scope.day, a : 1 });
};
}
};
});
Bind to callback with &
Create an object literal with a named property
<day-browser day-changed="changed(date, a)"></day-browser>
Signatur needs to match
date,a
44. Directive, child and parent directive
Typical scenarios are tab and tabitems, day vs calender
Parent need to talk to a child, and vice versa
<tabs>
<tab></tab>
<tab></tab>
<tab></tab>
</tabs>
Parent directive
Child directives
Behaviour :
Expand one tab, close the others,
like an accordion
45. Directive, the parent
angular
.module('app')
.directive('tabs', function () {
return {
restrict : 'E',
replace: true,
controller : function ($scope) {
var tabs = [];
this.addTab = function (tab) {
tabs.push(tab);
};
this.expand = function (tab) {
tabs.forEach(function (tab) {
tab.collapse = true;
});
$scope.$apply(function () {
tab.collapse = false;
});
};
}
};
});
Functions we want the child directives to call
WAIT, we are putting the functions on this, instead of $scope
Thats how angular wants it – deal with it
46. Directive, the child
angular
.module('app')
.directive('tab', function () {
return {
restrict : 'E',
replace: true,
scope : {
},
require: '^tabs',
template : '<div class="tab"><h1>tab header</h1><div ng-
hide="collapse" class="body">tab content</div></div>',
link : function (scope, element, attributes, tabs) {
scope.collapse = true;
tabs.addTab(scope);
element.on('click', function () {
var oldValue = scope.collapse;
scope.collapse = !oldValue;
if (!scope.collapse){
tabs.expand(scope);
}
});
}
}
});
Angular is walking the dom looking for the controller
47. Directives – decorative (attribute)
angular
.module('app')
.directive('expanderDirective', function() {
return {
link : function (scope, element, attributes) {
var bodyElem = element.find('.body');
var visibleBody = true;
element.on('click', function () {
scope.$apply(function () {
if (visibleBody) {
bodyElem.hide();
} else {
bodyElem.show();
}
visibleBody = !visibleBody;
});
});
}
}
});
<script src="bower_components/jquery/dist/jquery.js"></script>1
2 <script src="bower_components/angular/angular.js"></script>
<div expander-directive>
<div class="header">
header
</div>
<div class="body">
body text
</div>
</div>
Usage
Call $apply, when outside of angulars world
Reference jquery if you need
more power than jquery lite
50. Testing – setup config file
When you did karma init it created a config file.
files : {} // this is where you tell karma to find your application and your tests
frameworks: ['jasmine'] // for now it is jasmine could be qunit or something else
reporters: ['progress'] // this is where you specify things that can show you things like coverage
51. Testing – setup a test
1
2
3
Point to app and tests in config
Define test
Perform call
Assert4
files: [
'app/**/*.js*/',
'specs/**/*.js*/'
]
describe('given a calculator', function(){
var Calculator;
it('verify that addition works', function(){
var actual = Calculator.add(1,1)
expect().toBe(2);
});
52. Testing – setup an angular test
1
2
3
Import module and possible dependant modules
Import definition
Perform call
Assert4
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'app/**/*.js',
'specs/**/*.js'
]
0 Point to 1) angular + angular-mocks, 2 ) to app 3) tests
//load module
beforeEach(module('services'));
//load definition
beforeEach(inject(function(_Calculator_) {
Calculator = _Calculator_;
}));
it('verify that addition works', function(){
var actual = Calculator.add(1,1);
expect(actual).toBe(2);
});
53. Testing – angular test with dependency
describe('given a user service', function(){
var UserService;
//load modules
beforeEach(module('models'));
beforeEach(module('services'));
//load definition
beforeEach(inject(function(_userService_) {
UserService = _userService_;
}));
it('test parse', function(){
});
})
angular
.module('services')
.factory('userService', function(User, $http){
var that = {};
that.doStuff = function(){
};
that.get = function(){
return $http.get('/users/1');
};
return that;
});
54. Testing with a $http / promise
You DON’T want to go against the real backend so use $httpBackend, built in mockObject that intercepts $http
$httpBackend.whenGET(’someUrl’).respond(fakeData)
Also because $http calls returns a promise we need to call $httpBackend.flush() to resolve promises
Assume we have the following scenario:
userController => userService.getUser() => $http.get(’/users/1’);
In a test
$httpBackend.respond({ name : ’Zlatan’ })
Mock response
55. Testing a controller and a promise
describe('given a UserController', function(){
var UserController,
$scope,
ctrl,
$httpMock;
//load modules
beforeEach(module('models'));
beforeEach(module('services'));
beforeEach(module('controllers'));
//load definition
beforeEach(inject(function($controller, $rootScope, $httpBackend){
$httpMock = $httpBackend;
$httpMock.expectGET('/users/1').respond({ name : 'Zlatan' });
$scope = $rootScope.$new();
ctrl = $controller('userController', { $scope: $scope });
}));
it('verify I can get a user', function(){
$scope.load();
$httpMock.flush();
expect($scope.user.name).toBe('Zlatan');
});
})
Instruct $http mock to intercept
Construct controller
Resolve promises
Needed to create a new scope
56. Testing - mocking
describe('given a mathService', function(){
var Service;
beforeEach(module('services'));
beforeEach(function(){
module(function($provide){
$provide.factory('Calculator', function (){
return {
add : function(){
return 1+1;
}
};
});
});
});
beforeEach(inject(function(_mathService_) {
Service = _mathService_;
}));
it('verify that mathService add works', function(){
expect(Service.add(2,2)).toBe(4);
})
})
angular
.module('services')
.factory('mathService', function(Calculator){
var that = {};
that.add = function(lhs,rhs){
return Calculator.add(lhs,rhs);
};
that.sub = function(lhs,rhs){
return Calculator.sub(lhs,rhs);
};
return that;
});
Calculator
is replaced,
with a mock
57. Lab, setup config, write a test
Install karma
Try creating a test, try creating a test for an angular application
58. Task runners grunt / gulp
What problem do they solve?
During development, what do you need
On change:
jshint
Unit test
For deploy, what do you need to do
Uglify, js
Minify js
Compress js, css, html
59. Gulp
4 apis
gulp.task , defines a task, with gulp your run tasks
gulp.src ,points out one or several files
gulp.dest, points out a destination
gulp.watch, watches files for changes, reacts on save and then performs what you instructed it
61. Gulp, first task
gulp.task('copy', function(){
return gulp
.src('./copyfromhere/*.txt')
.pipe(gulp.dest('./tohere/'));
});
From where and which files
Copy to destination dir, pipe is so that
we keep working on the same stream,
no temp files like grunt
gulp copy //to run
62. Gulp - dependencies
gulp.task('default',['thenme'],function(){
console.log('running default...');
})
gulp.task('thenme', ['mefirst'], function(){
console.log('then me');
});
gulp.task('mefirst', function(){
console.log('me first');
});
[’task’] dependency, this is run before
The specified task
So mefirst then thenme and lastly default
default task doesn’t need to be specified
just run
gulp
63. Gulp - Building a deploy task
- We want to create as small of a foot print as possible, one or a few js-files, uglified
For js
◦ Concatenate into one or a few js files
◦ For angular, run ng-min to ensure names are preserved on dependencies
◦ Uglify, i.e compress, remove whitespace etc..
For css,
◦ run preprocessors like sass/less
◦ Concatenate
◦ Uglify
64. Gulp – deploy task code
gulp.task('build',[], function(){
console.log('running build..');
return gulp
.src('./app/**/*.js')
.pipe(concat('all.js'))
.pipe(uglify())
.pipe(gulp.dest('./dest/'));
});
Concatenate
Uglify
Place in dest folder
Depending on how complex your app is
This task might grow if you have many modules
65. Gulp – Building a monitor task
Well before checking in code we want to know if
- unit tests are green
- no problems with hint/lint
For this we can use a watch task, watch is part of gulp api
66. Gulp – monitor task code
gulp.task('watch',function(){
gulp.watch(['app/**/*.js','test/**/*.js'], ['lint','test']);
});
gulp.task('test', function() {
gulp.src(testFiles)
.pipe(karma({
configFile: 'karma.conf.js'
}));
});
gulp.task('lint', function(){
console.log('linting...');
return gulp
.src(['./app/**/*.js','./test/**/*.js'])
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
Run tests
Run jshint
On file change (and save) run