AngularJS training
Lauri Svan
lauri.svan@sc5.io @laurisvan
(original by kari.heikkinen@sc5.io)
Intro: About the Lecturer
● 15 year battle hardened veteran
● Head of Technology at SC5
● Helsinki Node.js co-founder
● In GitHub & Twitter
● “New school of engineering”
● Knows “why” & some “how” of
AngularJS
Intro: SC5
Yes, we’re hiring!
What is AngularJS
● Developed and maintained by Google
● Current version 1.5 (2.0 in Alpha)
● Good documentation and examples
● 3rd party libraries
● Insanely popular (see Bower, too)
● Easy to learn
● Get things done fast
● “Good enough”
Concepts
Two way data-binding
Dependency Injection (DI)
Application structure
Data driven development!
Recipe for an Angular Application
App Logic:
● 1 Module (the app)
● 1-N Templates for each view
● 1-N Controllers for each view
● 1 Router to toggle between UI states
● 1-N Directives for widgets
● 0-N Resources for each REST API
endpoints
● 0-N Services for inter-app comms &
storing app state
What else?
● Style sheets for app visuals
● Hybrid packaging (optional)
Modules
// Create module without dependencies
angular.module(‘myModule’, [ ] );
// Module with dependency
var app = angular.module(‘myApp’, [ ‘myModule’ ] );
// Get module and define controller
var module = angular.module(‘myModule’);
module.controller( ‘myController’, function($scope) {
$scope.title = ‘myController’;
});
// Declare application in HTML
<div ng-app=”myApp”>
// Declare application to body in javascript
angular.bootstrap( document.body, [‘myApp’] );
Template expressions
Angular template syntax uses double-curly brackets to
bind expression ‘{{ expression }}’.
<span>1+2={{ 1 + 2 }}</span>
{{ ‘Hello’ + ‘ World’ }}
{{ scopeVariable }}
{{ scopeFunction() }}
<img src=”{{ imageUrl }}”>
Controllers
● Controllers allow to interact with a View and Model.
● Is where to hold your presentation logic.
● Controller purpose is to drive Model and View
changes.
● New instance is created for each invocation
Controllers + $scope
Context where the model is stored so that controllers, directives and
expressions can access it.
$scope is a clever Object which is automated bridge between Javascript and
DOM that holds synchronized data.
var app = angular.module(‘myApp’);
app.controller(‘myCtrl’, function( $scope ) {
$scope.title = “MyTitle”;
});
app.controller(‘MySubCtrl’, function( $scope ) {
$scope.content = “MyData”;
});
<div ng-controller=”myCtrl”>
<h1>{{ title }}</h1>
<div ng-controller=”MySubCtrl”>
<p>{{content }}</p>
<span>ref: {{ title }}</span>
</div>
<div>{{content }}</div>
</div>
ControllerAs
New way in 1.3, similar as will be in 2.0
Easier to identify which scope is variable belongs to.
var app = angular.module(‘myApp’);
app.controller(‘myCtrl’, function() {
this.title = “MyTitle”;
});
app.controller(‘MySubCtrl’, function() {
this.content = “MyData”;
});
<div ng-controller=”myCtrl as mainCtrl”>
<h1>{{ mainCtrl.title }}</h1>
<div ng-controller=”MySubCtrl as subCtrl”>
<p>{{ subCtrl.content }}</p>
<span>ref: {{ mainCtrl.title }}</span>
</div>
<div>{{ subCtrl.content }}</div>
</div>
$scope.$watch
Way to react View change in the controller
app.controller(‘myCtrl’, function($scope) {
this.value = ‘Initial value’;
var _this = this;
$scope.$watch(
// return variable to watch (reference)
function() {
return _this.value;
},
// Handler function
function( newValue, oldValue ) {
console.log( oldValue, ‘->’, newValue );
}
);
});
app.controller(‘myCtrl’, function($scope) {
$scope.value = ‘Initial value’;
$scope.$watch(
‘value’,
function( newValue, oldValue ) {
console.log( oldValue, ‘->’, newValue );
}
);
});
W
ithout ControllerAs (1.2.x)
Services
● Use to hold data that persist application lifecycle, as
controllers are discarded when they are removed
from view.
● All services are singletons.
● Controllers access services via dependency injection.
● Three ways of creating services: service, factory, provider
Service
Creates service which will be invoked with ‘new’ to create
instance. (singleton instance)
app.service( ‘MyService’, function() {
this.greet = function() { alert(‘Hello!’); };
this.getText = function() { return ‘Hello!’; };
});
app.controller(‘myCtrl’, function(MyService) {
this.text = MyService.getText();
this.sayHello = function() {
MyService.greet();
}
});
var ServiceClass = function() {
this.color = ‘green’;
}
ServiceClass.prototype.setColor = function(color) {
this.color = color;
}
app.service( ‘MyService’, ServiceClass );
app.controller(‘MyController’, function(MyService) {
this.color = MyService.color;
this.onClick= function(color) {
MyService.setColor(color);
}
});
Factory
Register service by returning service instance object.
Can take advantage of closures.
app.factory( ‘MyService’, function() {
var greetText = “Hello”;
return {
greet: function() { alert(greetText); },
setText: function(text) { greetText = text; }
};
});
// Probably most common way to use factory
app.factory(‘Articles’, function( $resource, Settings ) {
return $resource( Settings.ApiHost + ‘/api/article’ );
}
app.controller(‘myCtrl’, function(MyService) {
this.text = MyService.getText();
this.sayHello = function() {
MyService.greet();
}
});
Providers
Only service definition that can be passed to config()
function.
Use to customize service on configuration phase.
app.provider( ‘MyService’, function() {
this.host = ‘/’;
this.$get = function( $resource ) {
return $resource( this.host + ‘/api/myservice’ );
};
});
app.config( function( MyServiceProvider ) {
if( window.location.host !== ‘example.com‘ )
MyServiceProvider.host = ‘example.com‘;
});
app.controller(‘myCtrl’, function(MyService) {
this.data = MyService.get( {id: 1234} );
});
Value & Constant
angular.module(‘myApp’).value( ‘Settings’, { host: ‘example.com’ } );
angular.module(‘myApp’).constant( ‘Config’, { host: ‘example.com’ } );
angular.module(‘myApp’).config( function(Config, MyServiceProvider ) {
MyServiceProvider.setApiHost( Config.host );
});
angular.module(‘myApp’).controller( ‘myCtrl’, function( Settings, $http ) {
var _this = this;
$http( Settings.host + ‘/api/data’ ).success( function(data) {
_this.data = data;
});
});
Filters
Filters are used for formatting data displayed to the
user.
Primarily used in expressions, but can be used in
controllers and services also.
{{ expression | filter1 | filter2 | ... }}
<span>{{ article.published | date:”yyyy-M-d” }}<span>
<span>{{ item.price | currency:”€” }}</span>
<label>{{ ‘ITEM_PRICE’ | translate }}</label>
<div ng-repeat=”person in persons | orderBy:’lastName’ | limitTo: 10”>{{ person.lastName}}</div>
// TIP: json filter is handy to check what object contains
<pre>{{ obj | json }}</pre>
Built-in filters
● currency - Format currency ( symbol, how many decimal numbers)
● number - To string, how many decimal numbers to use
● date - Format Date to string, use locale format as default
● json - Object to JSON string
● lowercase - Converts string to lowercase
● uppercase - Converts string to uppercase
● filter - select subset of array
● limitTo - creates new array with specified number of elements
● orderBy - Order array by the expression
Custom filter
app.filter( ‘translate’, function( LocalizationService ) {
return function( str ) {
return LocalizationService.getTranslation( str );
};
});
app.constant( ‘Settings’, { dateFormat: ‘d.M.yyyy’ } );
app.filter( ‘formatDate’, function( $filter, Settings ) {
var dateFilter = $filter.get(‘date’);
return function( date ) {
return dateFilter( date, Settings.dateFormat );
}
});
app.filter( ‘fullName’, function() {
return function( person ) { return person.firstName + ‘ ‘ + person.lastName; };
});
Directives
A Directive can be anything, it can either provide powerful logic to an
existing specific element, or be an element itself and provide an injected
template with powerful logic inside.
Directives are markers on a DOM element (such as an attribute, element
name, comment or CSS class) that tell AngularJS's HTML compiler to attach
a specified behavior to that DOM element or even transform the DOM
element and its children.
Angular comes with a set of built-in directives:
ng-model, ng-repeat, ng-show/ng-hide, ng-if, ng-click, ng-disabled, ng-
mouseover, ng-blur, ng-src/ng-href, ng-class, ng-switch, ng-bind, ng-view ….
Custom directives 1
Directives can match attribute name, tag name,
comments or class name. Or restricted only to match
some of them
<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>
Directives can emulate Shadow DOM behaviour with
option transclude
<my-element>
Hi there!
</my-element>
Custom directives 2
app.controller(‘myCtrl, function() {
this.person = { firstName: ‘John’, lastName: ‘Doe’ };
});
app.directive(‘person’, function() {
return {
template: ‘{{person.lastName}}, {{person.firstName}}’
};
});
<div ng-controller=”myCtrl”>
<h1>Hello <person></person></h1>,
<p>...</p>
</div>
Custom directives 3
app.directive(‘article’, function() {
return {
restrict: ‘EA’,
scope: { article: ‘=article’ },
templateUrl: ‘article.html
};
});
app.controller(‘myCtrl’, function() {
this.article = {
title: ‘Headline’,
content: ‘Lore ipsum’,
published: 1234554543543,
author: ‘John Doe’
}
});
article.html:
<article>
<header>
<h1>{{article.title}}</h1>
<p>Posted by {{ article.author }}</p>
<p>{{ article.published | date: ‘d.M.yyyy’ }}</p>
</header>
<p>{{ article.content }}</p>
</article>
index.html:
<article=”myCtrl.article”></article>
<div article=”MyCtrl.article”></div>
Custom directives 4
app.directive(‘article’, function() {
return {
restrict: ‘E’,
scope: { data: ‘=data },
bindToController: true,
templateUrl: ‘article.html,
controlelrAs, ‘article’,
controller: function() {
this.addComment = function(msg) {
this.data.comments.push(msg);
this.data.$save();
}
}
};
});
<article>
<header>
<h1>{{article.data.title}}</h1>
</header>
<p>{{ article.data.content }}</p>
<div class=comments>
<ul>
<li ng-repeat=”comment in article.data.comments”>{{ comment }}</li>
</ul>
<input ng-model=”newComment”/>
<button ng-click=”article.addComment(newComment)”>Send</button>
</div>
</article>
Custom directive 5 (transclude)
app.directive(“hideable”, function() {
return {
replace: true,
transclude: true,
template: [
‘<div ng-init=”hide=false”>’,
’<button ng-click=”hide=!hide”>Show/Hide</button>’,
‘<div ng-transclude ng-hide=”hide”></div>’,
‘</div>’
].join(‘’)
};
});
<div>
<h1> {{ title }} </h1>
<hideable>
<h2>{{ subTitle }}</h2>
<p>{{ content }}</p>
</hideable>
</div>
<div>
<h1> {{ title }} </h1>
<div ng-init=”hidden=false”>
<button ng-click=”hidden=!hidden”>Show/Hide</button>
<div ng-hide=”hidden”>
<h2>{{ subTitle }}</h2>
<p>{{ content }}</p>
</div>
</div>
</div>
Directive configuration
priority: 0,
template: '<div></div>', // or templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
transclude: false,
restrict: 'A',
templateNamespace: 'html',
scope: false,
controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
controllerAs: 'stringAlias',
bindToController: false,
require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) { ... },
post: function postLink(scope, iElement, iAttrs, controller) { ... }
}
},
// or
// link: function postLink( ... ) { ... }
Forms
Form and controls provide validation services, so that
the user can be notified of invalid input.
This provides a better user experience, because the user
gets instant feedback on how to correct the error.
Forms
<form name=”myFrom” novalidate>
<label>Email:</label>
<input type=”email” name=”email” ng-model=”user.email” required />
<label>Password:</label>
<input type=”password” name=”password” ng-model=”user.password” required minlength=”8” />
<div ng-messages=”myForm.password.$error”>
<div ng-message=”required”>Password is required</div>
<div ng-message=”minlength”>Password is too short</div>
</div>
<button ng-disabled=”myForm.$invalid”>Submit</button>
</form>
Form CSS classes
● ng-valid: the model is valid
● ng-invalid: the model is invalid
● ng-valid-[key]: for each valid key added by $setValidity
● ng-invalid-[key]: for each invalid key added by $setValidity
● 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
● ng-pending: any $asyncValidators are unfulfilled
Form CSS class example
<form name=”myFrom” novalidate>
<label>Email:</label>
<input
type=”email”
name=”email”
ng-model=”user.email”
required />
</form>
<style type="text/css">
form input.ng-invalid.ng-dirty {
outline-color: #FA787E;
}
</style>
Form validators
HTML5 input validators are built-in to Angular:
number, email, required, url, time, date, ..
Create own validators with directives:
<input name=”pwd”
type=”password
ng-model=”user.password”
required
minlength=”8”
validate-password-characters />
<div ng-messages=”myFrom.pwd.$error”>
<div ng-message=”required”>Password is required</div>
<div ng-message=”minlength”>Password is too short</div>
<div ng-message=”passwordCharacters”>Your password must contain a numeric, uppercase and ..
</div>
</div>
Custom validator
app.directive('validatePasswordCharacters', function() {
var REQUIRED_PATTERNS = [
/d+/, //numeric values
/[a-z]+/, //lowercase values
/[A-Z]+/, //uppercase values
/W+/, //special characters
/^S+$/ //no whitespace allowed
];
return {
require : 'ngModel',
link : function($scope, element, attrs, ngModel) {
ngModel.$validators.passwordCharacters = function(value) {
var status = true;
angular.forEach(REQUIRED_PATTERNS, function(pattern) {
status = status && pattern.test(value);
});
return status;
};
}
}
});
Async validator
angular.module(‘myApp’).directive(‘validateUsernameAvailable’, function($http) {
return {
require: ‘ngModel’,
link: function( scope, element, attr, ngModel ) {
ngModel.$asyncValidators.usernameAvailable = function(username) {
return $http.get(‘/api/username-exists?username=’ + username );
}
}
};
});
<input type=”text” name=”myUsername” validate-username-available />
<div ng-if="myForm.myUsername.$pending"> Checking Username… </div>
<div ng-if="myForm.myUsername.$error.usernameAvailable"> User name is in use </div>
<button ng-disabled=”!myForm.$valid”>Submit</button>
ngModelOptions
Validation and specially async validation may cause too
many calls for validation.
With ngModelOptions you can control when ngModel is
updated and validators are executed.
// Update when leaving input element
<input ng-model=”value” ng-model-options=”{ updateOn:’blur’ }” required />
// Update only when no changes in 500ms or immediately when leaving element
<input ng-model=”username” ng-model-options=”{ debounce: { default: 500, blur: 0 } }”
required validate-username-available />
Events
Angular scope have built-in event framework.
● $on - listen to event
● $emit - send event to upwards (self and parent
scopes)
● $broadcast - send event to downwards (self / child
scopes)
<div ng-controller="EventCtrl as parentCtrl" ng-scope>
<div ng-controller="EventCtrl as childCtrl1" ng-scope>
<div ng-controller="EventCtrl as subChildCtrl" ng-scope></div>
</div>
<div ng-controller="EventCtrl as childCtrl2" ng-scope>
</div>
</div>
$broadcast $send
Angular extension modules
● ngRoute - Routing and deeplinking
● ngResource - RESTful services
● ngAnimate - Support for JS, CSS transitions and
animations hooks
● ngSanitize - Bind HTML content safe way
● ngTouch - Touch events
● ngMessages - Enhanced support to show messages
ngRoute
To create routing for your Angular application include
ngRoute module and defined routes in config()-function.
angular.module(´myApp´, [´ngRoute´]).config(
function( $routeProvider, $locationProvider ) {
$routeProvider
.when('/', {
templateUrl: 'main.html',
controller: ´mainController´
}).
.when('/page1', {
templateUrl: page1.html',
controller: ´page1Controller´
}).
.otherwise({ redirectTo: ´/´ });
$locationProvider.html5Mode(true);
});
index.html:
<body>
<div ng-view> Route content will be here </div>
main.html:
<h1>main page</h1>
<a href=”/page”>goto to sub page</a>
page.html:
<h1>Page1 </h1>
<a href=”/”>back to main page</a>
ngRoute - resolve content
angular.module('myApp').config( function($routeProvider) {
$routeProvider.
when("/article/:id", {
templateUrl: 'article.html',
controller: 'ArticleCtrl',
controllerAs: ‘articleCtrl’,
resolve: {
article: function($resource, $route, $q, $location) {
var Article = $resource(‘/api/article/:id’);
var request = Article.get({id: $route.current.params.id});
request.$promise.catch( function() {
$location.path(‘/notfound’);
$location.replace();
});
return request.$promise;
}
}
});
});
angular.module(‘myApp’)
.controller( ‘articleCtrl’,
function( article ) {
this.data = article;
});
ngResource
Factory service which creates a resource object that lets
you to interact RESTful API.
Makes data handling object oriented.
var User = $resource(
‘/api/user/:id’,
{ id: ‘@_id’ },
{
update: { method: ‘PUT’ }
});
var person = new User({
firstName: ‘John’,
lastName:’doe’
});
person.$save();
person.lastName = ‘Doe’;
person.$update();
var person = new User({
firstName: ‘John’,
lastName:’doe’
});
person.$save(); // POST: ‘/api/user’
person.lastName = ‘Doe’;
person.$update(); // PUT: ‘/api/user/1’
// GET: ’/api/user?firstName=John’
var users = User.query({ firstName: ‘John });
var john = User.get({ id: 1234 }, function() {
john.lastLogin = new Date();
john.$update();
});
ngResource
Default configuration:
{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'} };
ngResource
var Article = $resource(‘/api/article/:id’, { id: ‘@_id’ }, {
recent: {
method: ‘GET’,
isArray: true,
cache: true, // use http cache for method
params: { limit: 10, latest: true }
}
});
// GET: /api/article?limit=10&latest=true&type=news
var latestArticles = Article.$latest( { type: ‘news’ } );
latestArticles.$promise
.success( function() {
// articles loaded
})
.catch( function() {
// request failed
});
ngAnimate
Adds animation hooks for common directives such as
ngRepeat, ngView and ngShow.
Also pays attention to CSS class changes with ngClass by
triggering add and remove hooks.
When change is triggered it places state CSS class, like
ng-hide, and end state ng-hide-active. And after
animation is ended it removes those classes
ngShow animate
<button ng-click=”hide=!hide”>Show/hide</button>
<div class=”animate” ng-show=”hide”>
Shown/hide with animation
</div>
<style>
.animate { opacity: 1; }
.animate.ng-hide { opacity: 0; }
.animate.ng-hide-add,
.animate.ng-hide-remove {
-webkit-transition: 1s linear all;
transition: 1s linear all;
}
</style>
JS animation with jQuery
angular.module('myApp',['ngAnimate']).animation('.animate', function() {
return {
beforeAddClass: function(element, className, done) {
if( className === 'ng-hide' )
element.animate({ opacity: 0 }, 500, done );
else
done();
},
removeClass: function(element, className, done) {
if( className === 'ng-hide' )
element.css({ opacity: 0 }).animate({ opacity: 1}, 500, done );
else
done();
}
}
})
Testing
AngularJS is designed that applications are testable.
Dependency Injection makes testing easier, by allowing
inject mock instead of real module.
Unit testing with Karma
my-ctrl.js:
angular.module('myApp',[])
.controller('myCtrl', function( $http ) {
var _this = this;
$http.get('/api/data').success(function(data) {
_this.data = data;
});
this.isOk = function() {
return this.data && this.data.ok;
}
});
my-ctrl.spec.js:
describe('controller:myCtrl', function() {
beforeEach( module('myApp') );
it('should fetch data from server and return ok',
inject(function($controller, $httpBackend) {
$httpBackend
.when('GET', '/api/data')
.respond({ ok: true });
$httpBackend.expectGET('/api/data');
var ctrl = $controller( 'myCtrl' );
expect(ctrl.isOk()).toBe(false);
$httpBackend.flush();
expect(ctrl.isOk()).toBe(true);
})
);
});
Dependency Injection in testing
it('should fetch data from server and return ok', inject(function($controller) {
var ctrlCB;
var myHttpMock = {
get: function() {
return {
success: function(cb) { ctrlCB= cb; }
};
}
}
var ctrl = $controller( 'myCtrl', { $http: myHttpMock } );
expect(ctrlCB).not.toBe(undefined);
cb( { ok: true } );
expect(ctrl.isOk()).toBe( true );
}));
End-to-end testing with protractor
describe('password view', function() {
it('should show error when too short input', function() {
browser.get('http://localhost:63342/angular-training/protractor/index.html');
element(by.model('user.password')).sendKeys('test');
var messages = element(by.id('password.error'));
expect(messages.getText()).toContain('short');
element(by.model('user.password')).sendKeys('test12345ABC-');
element(by.buttonText(‘save’)).click();
});
});
Best practices
● Design UI/HTML first!
Create HTML first and then just add functionality with
Angular. Style guide + UI mock-ups
● Avoid using $scope directly
By eliminating usage of $scope, code is closer to 2.0
style
● Avoid using ng-controller in HTML
Use through route or directives, do not add them
directly.
● Testing is easy!
Write tests for your code / site
Best practices
● If repeating elements in HTML, create directive for them
● Create services and use $resource to access server APIs
● Do not format data in the controllers, which will be displayed in the
view, instead use filter. Example, localisation, date/time, currency, etc.
Create filters that do it for you, and you can then modify those filters
and reflect changes to whole site.
● Avoid long lists which contains lots of bind data, that can cause
performance issues. Use paging to limit elements, if data changes in
page, otherwise use one-time binding (Angular 1.3), or libraries like
‘bindonce’ for Angular 1.2
● If you are binding without dot, you are probably doing something
wrong. {{ name }} = BAD, {{ user.name }} = GOOD
● Keep code structured
Caveats
● 2000 watches is considered as saturation point, after that page
rendering may seem slow
● Use directive isolated scope only when needed. Specially with
transclude, it can cause problems not to be able to access variables
● Namespace
Modules does not add namespace for functions. Plan function naming
(controller/service/directive/filter)
● Dependency Injection and javascript minify
DI breaks when minified. Declare in array syntax or use
ngAnnote/ngMin
● When using 3rd party libraries etc, remember to call $apply to get
changes render to page
Tools & Utilities for AngularJS
Boilerplates
https://github.com/SC5/gulp-bobrsass-boilerplate/tree/angularjs
https://github.com/DaftMonk/generator-angular-fullstack
Testing:
http://angular.github.io/protractor/#/
http://karma-runner.github.io/0.12/index.html
http://www.ng-newsletter.com/advent2013/#!/day/19
Build and minification:
https://github.com/olov/ng-annotate
More about AngularJS
Community in Helsinki capital area
https://www.facebook.com/groups/helsinkijs/
http://frontend.fi/
AngularJS Primer
https://www.airpair.com/angularjs/posts/angularjs-tutorial
https://thinkster.io/angulartutorial/a-better-way-to-learn-angularjs/
http://lostechies.com/gabrielschenker/2013/12/05/angularjspart-1/
Blogs that have good information about AngularJS:
http://www.yearofmoo.com/
http://www.jvandemo.com/
Good reads:
http://sc5.io/posts/how-to-implement-loaders-for-an-angularjs-app
http://teropa.info/blog/2014/11/01/why-i-think-angular-2-will-still-be-angular.html
Conclusion and discussion
What did we learn?
Discussion about AngularJS
Test setup
● Install Node.js for test server
http://nodejs.org/download/
● Download and extract training examples
https://docs.google.com/a/sc5.io/uc?
authuser=0&id=0B_18Pna_ughFNWVZaUV1amV6bjA&export=download
● Go to exercises folder and execute commands
> npm install
> npm start
● Open local web server with browser
http://localhost:3000/

Angular.js Primer in Aalto University

  • 1.
    AngularJS training Lauri Svan lauri.svan@sc5.io@laurisvan (original by kari.heikkinen@sc5.io)
  • 2.
    Intro: About theLecturer ● 15 year battle hardened veteran ● Head of Technology at SC5 ● Helsinki Node.js co-founder ● In GitHub & Twitter ● “New school of engineering” ● Knows “why” & some “how” of AngularJS
  • 3.
  • 4.
    What is AngularJS ●Developed and maintained by Google ● Current version 1.5 (2.0 in Alpha) ● Good documentation and examples ● 3rd party libraries ● Insanely popular (see Bower, too) ● Easy to learn ● Get things done fast ● “Good enough”
  • 5.
    Concepts Two way data-binding DependencyInjection (DI) Application structure Data driven development!
  • 6.
    Recipe for anAngular Application App Logic: ● 1 Module (the app) ● 1-N Templates for each view ● 1-N Controllers for each view ● 1 Router to toggle between UI states ● 1-N Directives for widgets ● 0-N Resources for each REST API endpoints ● 0-N Services for inter-app comms & storing app state What else? ● Style sheets for app visuals ● Hybrid packaging (optional)
  • 7.
    Modules // Create modulewithout dependencies angular.module(‘myModule’, [ ] ); // Module with dependency var app = angular.module(‘myApp’, [ ‘myModule’ ] ); // Get module and define controller var module = angular.module(‘myModule’); module.controller( ‘myController’, function($scope) { $scope.title = ‘myController’; }); // Declare application in HTML <div ng-app=”myApp”> // Declare application to body in javascript angular.bootstrap( document.body, [‘myApp’] );
  • 8.
    Template expressions Angular templatesyntax uses double-curly brackets to bind expression ‘{{ expression }}’. <span>1+2={{ 1 + 2 }}</span> {{ ‘Hello’ + ‘ World’ }} {{ scopeVariable }} {{ scopeFunction() }} <img src=”{{ imageUrl }}”>
  • 9.
    Controllers ● Controllers allowto interact with a View and Model. ● Is where to hold your presentation logic. ● Controller purpose is to drive Model and View changes. ● New instance is created for each invocation
  • 10.
    Controllers + $scope Contextwhere the model is stored so that controllers, directives and expressions can access it. $scope is a clever Object which is automated bridge between Javascript and DOM that holds synchronized data. var app = angular.module(‘myApp’); app.controller(‘myCtrl’, function( $scope ) { $scope.title = “MyTitle”; }); app.controller(‘MySubCtrl’, function( $scope ) { $scope.content = “MyData”; }); <div ng-controller=”myCtrl”> <h1>{{ title }}</h1> <div ng-controller=”MySubCtrl”> <p>{{content }}</p> <span>ref: {{ title }}</span> </div> <div>{{content }}</div> </div>
  • 11.
    ControllerAs New way in1.3, similar as will be in 2.0 Easier to identify which scope is variable belongs to. var app = angular.module(‘myApp’); app.controller(‘myCtrl’, function() { this.title = “MyTitle”; }); app.controller(‘MySubCtrl’, function() { this.content = “MyData”; }); <div ng-controller=”myCtrl as mainCtrl”> <h1>{{ mainCtrl.title }}</h1> <div ng-controller=”MySubCtrl as subCtrl”> <p>{{ subCtrl.content }}</p> <span>ref: {{ mainCtrl.title }}</span> </div> <div>{{ subCtrl.content }}</div> </div>
  • 12.
    $scope.$watch Way to reactView change in the controller app.controller(‘myCtrl’, function($scope) { this.value = ‘Initial value’; var _this = this; $scope.$watch( // return variable to watch (reference) function() { return _this.value; }, // Handler function function( newValue, oldValue ) { console.log( oldValue, ‘->’, newValue ); } ); }); app.controller(‘myCtrl’, function($scope) { $scope.value = ‘Initial value’; $scope.$watch( ‘value’, function( newValue, oldValue ) { console.log( oldValue, ‘->’, newValue ); } ); }); W ithout ControllerAs (1.2.x)
  • 13.
    Services ● Use tohold data that persist application lifecycle, as controllers are discarded when they are removed from view. ● All services are singletons. ● Controllers access services via dependency injection. ● Three ways of creating services: service, factory, provider
  • 14.
    Service Creates service whichwill be invoked with ‘new’ to create instance. (singleton instance) app.service( ‘MyService’, function() { this.greet = function() { alert(‘Hello!’); }; this.getText = function() { return ‘Hello!’; }; }); app.controller(‘myCtrl’, function(MyService) { this.text = MyService.getText(); this.sayHello = function() { MyService.greet(); } }); var ServiceClass = function() { this.color = ‘green’; } ServiceClass.prototype.setColor = function(color) { this.color = color; } app.service( ‘MyService’, ServiceClass ); app.controller(‘MyController’, function(MyService) { this.color = MyService.color; this.onClick= function(color) { MyService.setColor(color); } });
  • 15.
    Factory Register service byreturning service instance object. Can take advantage of closures. app.factory( ‘MyService’, function() { var greetText = “Hello”; return { greet: function() { alert(greetText); }, setText: function(text) { greetText = text; } }; }); // Probably most common way to use factory app.factory(‘Articles’, function( $resource, Settings ) { return $resource( Settings.ApiHost + ‘/api/article’ ); } app.controller(‘myCtrl’, function(MyService) { this.text = MyService.getText(); this.sayHello = function() { MyService.greet(); } });
  • 16.
    Providers Only service definitionthat can be passed to config() function. Use to customize service on configuration phase. app.provider( ‘MyService’, function() { this.host = ‘/’; this.$get = function( $resource ) { return $resource( this.host + ‘/api/myservice’ ); }; }); app.config( function( MyServiceProvider ) { if( window.location.host !== ‘example.com‘ ) MyServiceProvider.host = ‘example.com‘; }); app.controller(‘myCtrl’, function(MyService) { this.data = MyService.get( {id: 1234} ); });
  • 17.
    Value & Constant angular.module(‘myApp’).value(‘Settings’, { host: ‘example.com’ } ); angular.module(‘myApp’).constant( ‘Config’, { host: ‘example.com’ } ); angular.module(‘myApp’).config( function(Config, MyServiceProvider ) { MyServiceProvider.setApiHost( Config.host ); }); angular.module(‘myApp’).controller( ‘myCtrl’, function( Settings, $http ) { var _this = this; $http( Settings.host + ‘/api/data’ ).success( function(data) { _this.data = data; }); });
  • 18.
    Filters Filters are usedfor formatting data displayed to the user. Primarily used in expressions, but can be used in controllers and services also. {{ expression | filter1 | filter2 | ... }} <span>{{ article.published | date:”yyyy-M-d” }}<span> <span>{{ item.price | currency:”€” }}</span> <label>{{ ‘ITEM_PRICE’ | translate }}</label> <div ng-repeat=”person in persons | orderBy:’lastName’ | limitTo: 10”>{{ person.lastName}}</div> // TIP: json filter is handy to check what object contains <pre>{{ obj | json }}</pre>
  • 19.
    Built-in filters ● currency- Format currency ( symbol, how many decimal numbers) ● number - To string, how many decimal numbers to use ● date - Format Date to string, use locale format as default ● json - Object to JSON string ● lowercase - Converts string to lowercase ● uppercase - Converts string to uppercase ● filter - select subset of array ● limitTo - creates new array with specified number of elements ● orderBy - Order array by the expression
  • 20.
    Custom filter app.filter( ‘translate’,function( LocalizationService ) { return function( str ) { return LocalizationService.getTranslation( str ); }; }); app.constant( ‘Settings’, { dateFormat: ‘d.M.yyyy’ } ); app.filter( ‘formatDate’, function( $filter, Settings ) { var dateFilter = $filter.get(‘date’); return function( date ) { return dateFilter( date, Settings.dateFormat ); } }); app.filter( ‘fullName’, function() { return function( person ) { return person.firstName + ‘ ‘ + person.lastName; }; });
  • 21.
    Directives A Directive canbe anything, it can either provide powerful logic to an existing specific element, or be an element itself and provide an injected template with powerful logic inside. Directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler to attach a specified behavior to that DOM element or even transform the DOM element and its children. Angular comes with a set of built-in directives: ng-model, ng-repeat, ng-show/ng-hide, ng-if, ng-click, ng-disabled, ng- mouseover, ng-blur, ng-src/ng-href, ng-class, ng-switch, ng-bind, ng-view ….
  • 22.
    Custom directives 1 Directivescan match attribute name, tag name, comments or class name. Or restricted only to match some of them <my-dir></my-dir> <span my-dir="exp"></span> <!-- directive: my-dir exp --> <span class="my-dir: exp;"></span> Directives can emulate Shadow DOM behaviour with option transclude <my-element> Hi there! </my-element>
  • 23.
    Custom directives 2 app.controller(‘myCtrl,function() { this.person = { firstName: ‘John’, lastName: ‘Doe’ }; }); app.directive(‘person’, function() { return { template: ‘{{person.lastName}}, {{person.firstName}}’ }; }); <div ng-controller=”myCtrl”> <h1>Hello <person></person></h1>, <p>...</p> </div>
  • 24.
    Custom directives 3 app.directive(‘article’,function() { return { restrict: ‘EA’, scope: { article: ‘=article’ }, templateUrl: ‘article.html }; }); app.controller(‘myCtrl’, function() { this.article = { title: ‘Headline’, content: ‘Lore ipsum’, published: 1234554543543, author: ‘John Doe’ } }); article.html: <article> <header> <h1>{{article.title}}</h1> <p>Posted by {{ article.author }}</p> <p>{{ article.published | date: ‘d.M.yyyy’ }}</p> </header> <p>{{ article.content }}</p> </article> index.html: <article=”myCtrl.article”></article> <div article=”MyCtrl.article”></div>
  • 25.
    Custom directives 4 app.directive(‘article’,function() { return { restrict: ‘E’, scope: { data: ‘=data }, bindToController: true, templateUrl: ‘article.html, controlelrAs, ‘article’, controller: function() { this.addComment = function(msg) { this.data.comments.push(msg); this.data.$save(); } } }; }); <article> <header> <h1>{{article.data.title}}</h1> </header> <p>{{ article.data.content }}</p> <div class=comments> <ul> <li ng-repeat=”comment in article.data.comments”>{{ comment }}</li> </ul> <input ng-model=”newComment”/> <button ng-click=”article.addComment(newComment)”>Send</button> </div> </article>
  • 26.
    Custom directive 5(transclude) app.directive(“hideable”, function() { return { replace: true, transclude: true, template: [ ‘<div ng-init=”hide=false”>’, ’<button ng-click=”hide=!hide”>Show/Hide</button>’, ‘<div ng-transclude ng-hide=”hide”></div>’, ‘</div>’ ].join(‘’) }; }); <div> <h1> {{ title }} </h1> <hideable> <h2>{{ subTitle }}</h2> <p>{{ content }}</p> </hideable> </div> <div> <h1> {{ title }} </h1> <div ng-init=”hidden=false”> <button ng-click=”hidden=!hidden”>Show/Hide</button> <div ng-hide=”hidden”> <h2>{{ subTitle }}</h2> <p>{{ content }}</p> </div> </div> </div>
  • 27.
    Directive configuration priority: 0, template:'<div></div>', // or templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, transclude: false, restrict: 'A', templateNamespace: 'html', scope: false, controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, controllerAs: 'stringAlias', bindToController: false, require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], compile: function compile(tElement, tAttrs, transclude) { return { pre: function preLink(scope, iElement, iAttrs, controller) { ... }, post: function postLink(scope, iElement, iAttrs, controller) { ... } } }, // or // link: function postLink( ... ) { ... }
  • 28.
    Forms Form and controlsprovide validation services, so that the user can be notified of invalid input. This provides a better user experience, because the user gets instant feedback on how to correct the error.
  • 29.
    Forms <form name=”myFrom” novalidate> <label>Email:</label> <inputtype=”email” name=”email” ng-model=”user.email” required /> <label>Password:</label> <input type=”password” name=”password” ng-model=”user.password” required minlength=”8” /> <div ng-messages=”myForm.password.$error”> <div ng-message=”required”>Password is required</div> <div ng-message=”minlength”>Password is too short</div> </div> <button ng-disabled=”myForm.$invalid”>Submit</button> </form>
  • 30.
    Form CSS classes ●ng-valid: the model is valid ● ng-invalid: the model is invalid ● ng-valid-[key]: for each valid key added by $setValidity ● ng-invalid-[key]: for each invalid key added by $setValidity ● 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 ● ng-pending: any $asyncValidators are unfulfilled
  • 31.
    Form CSS classexample <form name=”myFrom” novalidate> <label>Email:</label> <input type=”email” name=”email” ng-model=”user.email” required /> </form> <style type="text/css"> form input.ng-invalid.ng-dirty { outline-color: #FA787E; } </style>
  • 32.
    Form validators HTML5 inputvalidators are built-in to Angular: number, email, required, url, time, date, .. Create own validators with directives: <input name=”pwd” type=”password ng-model=”user.password” required minlength=”8” validate-password-characters /> <div ng-messages=”myFrom.pwd.$error”> <div ng-message=”required”>Password is required</div> <div ng-message=”minlength”>Password is too short</div> <div ng-message=”passwordCharacters”>Your password must contain a numeric, uppercase and .. </div> </div>
  • 33.
    Custom validator app.directive('validatePasswordCharacters', function(){ var REQUIRED_PATTERNS = [ /d+/, //numeric values /[a-z]+/, //lowercase values /[A-Z]+/, //uppercase values /W+/, //special characters /^S+$/ //no whitespace allowed ]; return { require : 'ngModel', link : function($scope, element, attrs, ngModel) { ngModel.$validators.passwordCharacters = function(value) { var status = true; angular.forEach(REQUIRED_PATTERNS, function(pattern) { status = status && pattern.test(value); }); return status; }; } } });
  • 34.
    Async validator angular.module(‘myApp’).directive(‘validateUsernameAvailable’, function($http){ return { require: ‘ngModel’, link: function( scope, element, attr, ngModel ) { ngModel.$asyncValidators.usernameAvailable = function(username) { return $http.get(‘/api/username-exists?username=’ + username ); } } }; }); <input type=”text” name=”myUsername” validate-username-available /> <div ng-if="myForm.myUsername.$pending"> Checking Username… </div> <div ng-if="myForm.myUsername.$error.usernameAvailable"> User name is in use </div> <button ng-disabled=”!myForm.$valid”>Submit</button>
  • 35.
    ngModelOptions Validation and speciallyasync validation may cause too many calls for validation. With ngModelOptions you can control when ngModel is updated and validators are executed. // Update when leaving input element <input ng-model=”value” ng-model-options=”{ updateOn:’blur’ }” required /> // Update only when no changes in 500ms or immediately when leaving element <input ng-model=”username” ng-model-options=”{ debounce: { default: 500, blur: 0 } }” required validate-username-available />
  • 36.
    Events Angular scope havebuilt-in event framework. ● $on - listen to event ● $emit - send event to upwards (self and parent scopes) ● $broadcast - send event to downwards (self / child scopes) <div ng-controller="EventCtrl as parentCtrl" ng-scope> <div ng-controller="EventCtrl as childCtrl1" ng-scope> <div ng-controller="EventCtrl as subChildCtrl" ng-scope></div> </div> <div ng-controller="EventCtrl as childCtrl2" ng-scope> </div> </div> $broadcast $send
  • 37.
    Angular extension modules ●ngRoute - Routing and deeplinking ● ngResource - RESTful services ● ngAnimate - Support for JS, CSS transitions and animations hooks ● ngSanitize - Bind HTML content safe way ● ngTouch - Touch events ● ngMessages - Enhanced support to show messages
  • 38.
    ngRoute To create routingfor your Angular application include ngRoute module and defined routes in config()-function. angular.module(´myApp´, [´ngRoute´]).config( function( $routeProvider, $locationProvider ) { $routeProvider .when('/', { templateUrl: 'main.html', controller: ´mainController´ }). .when('/page1', { templateUrl: page1.html', controller: ´page1Controller´ }). .otherwise({ redirectTo: ´/´ }); $locationProvider.html5Mode(true); }); index.html: <body> <div ng-view> Route content will be here </div> main.html: <h1>main page</h1> <a href=”/page”>goto to sub page</a> page.html: <h1>Page1 </h1> <a href=”/”>back to main page</a>
  • 39.
    ngRoute - resolvecontent angular.module('myApp').config( function($routeProvider) { $routeProvider. when("/article/:id", { templateUrl: 'article.html', controller: 'ArticleCtrl', controllerAs: ‘articleCtrl’, resolve: { article: function($resource, $route, $q, $location) { var Article = $resource(‘/api/article/:id’); var request = Article.get({id: $route.current.params.id}); request.$promise.catch( function() { $location.path(‘/notfound’); $location.replace(); }); return request.$promise; } } }); }); angular.module(‘myApp’) .controller( ‘articleCtrl’, function( article ) { this.data = article; });
  • 40.
    ngResource Factory service whichcreates a resource object that lets you to interact RESTful API. Makes data handling object oriented. var User = $resource( ‘/api/user/:id’, { id: ‘@_id’ }, { update: { method: ‘PUT’ } }); var person = new User({ firstName: ‘John’, lastName:’doe’ }); person.$save(); person.lastName = ‘Doe’; person.$update(); var person = new User({ firstName: ‘John’, lastName:’doe’ }); person.$save(); // POST: ‘/api/user’ person.lastName = ‘Doe’; person.$update(); // PUT: ‘/api/user/1’ // GET: ’/api/user?firstName=John’ var users = User.query({ firstName: ‘John }); var john = User.get({ id: 1234 }, function() { john.lastLogin = new Date(); john.$update(); });
  • 41.
    ngResource Default configuration: { 'get':{method:'GET'}, 'save': {method:'POST'}, 'query': {method:'GET', isArray:true}, 'remove': {method:'DELETE'}, 'delete': {method:'DELETE'} };
  • 42.
    ngResource var Article =$resource(‘/api/article/:id’, { id: ‘@_id’ }, { recent: { method: ‘GET’, isArray: true, cache: true, // use http cache for method params: { limit: 10, latest: true } } }); // GET: /api/article?limit=10&latest=true&type=news var latestArticles = Article.$latest( { type: ‘news’ } ); latestArticles.$promise .success( function() { // articles loaded }) .catch( function() { // request failed });
  • 43.
    ngAnimate Adds animation hooksfor common directives such as ngRepeat, ngView and ngShow. Also pays attention to CSS class changes with ngClass by triggering add and remove hooks. When change is triggered it places state CSS class, like ng-hide, and end state ng-hide-active. And after animation is ended it removes those classes
  • 44.
    ngShow animate <button ng-click=”hide=!hide”>Show/hide</button> <divclass=”animate” ng-show=”hide”> Shown/hide with animation </div> <style> .animate { opacity: 1; } .animate.ng-hide { opacity: 0; } .animate.ng-hide-add, .animate.ng-hide-remove { -webkit-transition: 1s linear all; transition: 1s linear all; } </style>
  • 45.
    JS animation withjQuery angular.module('myApp',['ngAnimate']).animation('.animate', function() { return { beforeAddClass: function(element, className, done) { if( className === 'ng-hide' ) element.animate({ opacity: 0 }, 500, done ); else done(); }, removeClass: function(element, className, done) { if( className === 'ng-hide' ) element.css({ opacity: 0 }).animate({ opacity: 1}, 500, done ); else done(); } } })
  • 46.
    Testing AngularJS is designedthat applications are testable. Dependency Injection makes testing easier, by allowing inject mock instead of real module.
  • 47.
    Unit testing withKarma my-ctrl.js: angular.module('myApp',[]) .controller('myCtrl', function( $http ) { var _this = this; $http.get('/api/data').success(function(data) { _this.data = data; }); this.isOk = function() { return this.data && this.data.ok; } }); my-ctrl.spec.js: describe('controller:myCtrl', function() { beforeEach( module('myApp') ); it('should fetch data from server and return ok', inject(function($controller, $httpBackend) { $httpBackend .when('GET', '/api/data') .respond({ ok: true }); $httpBackend.expectGET('/api/data'); var ctrl = $controller( 'myCtrl' ); expect(ctrl.isOk()).toBe(false); $httpBackend.flush(); expect(ctrl.isOk()).toBe(true); }) ); });
  • 48.
    Dependency Injection intesting it('should fetch data from server and return ok', inject(function($controller) { var ctrlCB; var myHttpMock = { get: function() { return { success: function(cb) { ctrlCB= cb; } }; } } var ctrl = $controller( 'myCtrl', { $http: myHttpMock } ); expect(ctrlCB).not.toBe(undefined); cb( { ok: true } ); expect(ctrl.isOk()).toBe( true ); }));
  • 49.
    End-to-end testing withprotractor describe('password view', function() { it('should show error when too short input', function() { browser.get('http://localhost:63342/angular-training/protractor/index.html'); element(by.model('user.password')).sendKeys('test'); var messages = element(by.id('password.error')); expect(messages.getText()).toContain('short'); element(by.model('user.password')).sendKeys('test12345ABC-'); element(by.buttonText(‘save’)).click(); }); });
  • 50.
    Best practices ● DesignUI/HTML first! Create HTML first and then just add functionality with Angular. Style guide + UI mock-ups ● Avoid using $scope directly By eliminating usage of $scope, code is closer to 2.0 style ● Avoid using ng-controller in HTML Use through route or directives, do not add them directly. ● Testing is easy! Write tests for your code / site
  • 51.
    Best practices ● Ifrepeating elements in HTML, create directive for them ● Create services and use $resource to access server APIs ● Do not format data in the controllers, which will be displayed in the view, instead use filter. Example, localisation, date/time, currency, etc. Create filters that do it for you, and you can then modify those filters and reflect changes to whole site. ● Avoid long lists which contains lots of bind data, that can cause performance issues. Use paging to limit elements, if data changes in page, otherwise use one-time binding (Angular 1.3), or libraries like ‘bindonce’ for Angular 1.2 ● If you are binding without dot, you are probably doing something wrong. {{ name }} = BAD, {{ user.name }} = GOOD ● Keep code structured
  • 52.
    Caveats ● 2000 watchesis considered as saturation point, after that page rendering may seem slow ● Use directive isolated scope only when needed. Specially with transclude, it can cause problems not to be able to access variables ● Namespace Modules does not add namespace for functions. Plan function naming (controller/service/directive/filter) ● Dependency Injection and javascript minify DI breaks when minified. Declare in array syntax or use ngAnnote/ngMin ● When using 3rd party libraries etc, remember to call $apply to get changes render to page
  • 53.
    Tools & Utilitiesfor AngularJS Boilerplates https://github.com/SC5/gulp-bobrsass-boilerplate/tree/angularjs https://github.com/DaftMonk/generator-angular-fullstack Testing: http://angular.github.io/protractor/#/ http://karma-runner.github.io/0.12/index.html http://www.ng-newsletter.com/advent2013/#!/day/19 Build and minification: https://github.com/olov/ng-annotate
  • 54.
    More about AngularJS Communityin Helsinki capital area https://www.facebook.com/groups/helsinkijs/ http://frontend.fi/ AngularJS Primer https://www.airpair.com/angularjs/posts/angularjs-tutorial https://thinkster.io/angulartutorial/a-better-way-to-learn-angularjs/ http://lostechies.com/gabrielschenker/2013/12/05/angularjspart-1/ Blogs that have good information about AngularJS: http://www.yearofmoo.com/ http://www.jvandemo.com/ Good reads: http://sc5.io/posts/how-to-implement-loaders-for-an-angularjs-app http://teropa.info/blog/2014/11/01/why-i-think-angular-2-will-still-be-angular.html
  • 55.
    Conclusion and discussion Whatdid we learn? Discussion about AngularJS
  • 56.
    Test setup ● InstallNode.js for test server http://nodejs.org/download/ ● Download and extract training examples https://docs.google.com/a/sc5.io/uc? authuser=0&id=0B_18Pna_ughFNWVZaUV1amV6bjA&export=download ● Go to exercises folder and execute commands > npm install > npm start ● Open local web server with browser http://localhost:3000/