Download - AngularJS Workshop
Summary● What is AngularJS
○ The Web before AngularJS○ Traditional Web Applications, SPAs
● How an AngularJS app is made○ Modules, Controllers, Directives○ Services, Providers, Factories
● Let’s build something○ Angular Lab: build your own blog
● Advanced topics○ Routing, custom directives, Transclusion, Directive-
to-Directive Communication
Legacy Javascript
● A lot of boilerplate code● Hard coupling between UI and business logic● No cross-browser compatibility
var box = document.getElementById('box'), hasClass = function (el, cl) { var regex = new RegExp('(?:s|^)' + cl + '(?:s|$)'); return !!el.className.match(regex); }, addClass = function (el, cl) { el.className += ' ' + cl; }, removeClass = function (el, cl) { var regex = new RegExp('(?:s|^)' + cl + '(?:s|$)'); el.className = el.className.replace(regex, ' '); }, toggleClass = function (el, cl) { hasClass(el, cl) ? removeClass(el, cl) : addClass(el, cl); };addClass(box, 'wrap');removeClass(box, 'wrap');toggleClass(box, 'wrap');
Legacy Javascript
I’m a box!I’m a box!
#box
$('#box').addClass('wrap');
$('#box').removeClass('wrap');
$('#box').toggleClass('wrap');
● Several OSs (Win, MacOSX, Linux, Mobile)● Mobile devices are performant● HTML5, CSS3, SVG give a better UX● Write once, run everywhere
The concept of app is evolving...
● Website user interfaces are becoming like standalone applications
● Fast loading of server-side data
so...
Single Page Application (SPA)
Client Server
GET /
GET /api/blog/posts
[{ "id": 123, "title": "Hello!" }, { "id": 125, "title": "Bye!" }]
What is AngularJS?
● A structural framework for dynamic web apps● A solution for creating SPA (One Page Application)● MVC done right (separation of application logic, data
models and views)● A new way to enrich HTML, turning it in a declarative
language● Data models are plain Javascript objects● Context aware communication● Dependency Injection (Increased testability)
Architecture overview
Module
Config Filter Directive Factory Controller
Routes Service
Provider
Value
Module
You can think of a module as a container for the different parts of your app – controllers, services, filters, directives, etc.
angular.module("fooLib", []);
var myApp = angular.module("myApp", ["fooLib"]);
In Java this concept is close to packages
Controller
● Business logic for user interaction● Model-View wiring code
angular.module("myApp", []).controller("MainCtrl", function($scope) {
$scope.name = "John Doe";$scope.countChars = function(text) {
return text.length;}
});
Directive
● Reusable component (like a widget)● View + Controller● Declarative HTMLangular.module("myApp", [])
.directive("movieCard", function() {return {
templateUrl: "partials/movie-card.html",controller: function($scope, $element) {
[...]}
};});
Service/Provider/Factory
● Lazy-loaded Singleton object● Provide a method to keep data around for
the lifetime of the app● Communicate across controllers in a
consistent manner
angular.module("myApp", []).factory("UserService", function($http) {
var _currUser;return {
getCurrentUser: function() { return _currUser; },setCurrentUser: function(user) { _currUser = user; }
}});
The basics<html><head>
<script src="js/angular.min.js"></script><script src="js/app.js"></script>
</head><body>
<div ng-app="myFirstApp"></div>
</body></head></html>
The library
ng-app: boundary of an AngularJS app
Our app components
Setup Labs Environment
1. download labs here: https://goo.gl/cVI6De2. open the workshop directory in Intellij IDEA3. open the terminal (bottom left)4. npm install5. npm start
http://jsbin.com/mejeno/edit
● Declarative HTML● Automatic data binding
(two-way data binding)● Easy composability● Application state
organized in scopes
● IDs everywhere● Low level DOM events
management● Mixed UI and
behavioural logic● Require knowledge of
the entire DOM structure
< ng-lab="0" />
You will surely write less code!
● Registering callbacks● Manipulating HTML DOM programmatically● Marshalling data to and from the UI● Writing tons of initialization code just to get
started
● Registering callbacks● Manipulating HTML DOM programmatically● Marshalling data to and from the UI● Writing tons of initialization code just to get
started
The magic of two-way data binding● In classic web frameworks (no javascript),
view reflects the model at render time.
Template
HTML
Model
template.render(model)
ViewHTML
Model snapshot
The magic of two-way data binding● In AngularJS the template is compiled into a
reactive view, bindable to a changing model
ViewHTML
Reactive components
linkFn = $compile(template)
Model
linkFn($scope)
Template
HTML with directives
Scope
● Source of truth for the application state● The glue between application controller and
the view● Follows a hierarchical structure● Can propagate events and monitor watch
expressions
Scope Inheritance
$scope
● Child scope(prototypical inheritance)
● Isolated scope(no properties inheritance)
p1
p2
$scopep1
p2p3
$parent
$scopep1
p2
$scopep3
$parent
Scope Events
● Watch on scope changes● Publish - Subscribe paradigm● Powerful to keep components decoupled
$watch $broadcast $emit $onRegister to an expression on the model
Publish from parent to children
Publish from child to parents
Subscribe to incoming events
Scope Events - $watch
var fooBar; $scope
$scope
$scope.$watch("fooBar", function(newValue, oldValue) { console.log(newValue) });
ViewEnter fooBar:
My foobar
$watch out for {{ }}
● Every time there is a change on the $scope, expressions on it and its children are evaluated and the result, if changed, reflected into the DOM
● Dangerous for long tables rendering (thousands of watches)
One-time binding to the rescue!
<div>{{ ::person.firstName }}</div>
Scope Events - $broadcast
$scope
$scope $scope
$scope$scope.$on("fooBar", function(evt, msg) {
console.log(msg);});
$scope.$broadcast("fooBar", "This is an important message!");
Scope Events - $emit
$scope
$scope $scope
$scope
$scope.$on("fooBar", function(evt, msg) {
console.log(msg);});
$scope.$emit("fooBar", "This is an important message!");
$scope.$on("fooBar", function(evt, msg) {
console.log(msg);});
Who can create scopes?
Controllers● Using ng-controller● Scopes can only be
child scopes(prototypical inheritance)
Directives● With a specific
configuration, when they are included in the HTML
● Scopes can be both child or isolated
Built-in DirectivesAll the ng- directives come from the core library:
● ng-show or ng-hide: show or hide a component● ng-if: adds or removes a component from the DOM● ng-switch: allocates or deallocates a component on the
DOM based on a switch-case logic● ng-click: runs an expression when clicking on a
component● ng-repeat: iterates an array and repeat a component● ng-model: binds a form component to a scope property● ng-class: conditionally adds or removes CSS classes
Expressions
● Used all over AngularJS apps● Executed in the current $scope context and have
access to $scope properties● They don’t throw errors if it’s TypeError or
ReferenceError● They don’t allow for any control flow functions (e.g.,
if/else)● They can accept a filter and/or filter chains
<div>{{ person.firstName }}</div><div>{{ 5 * 2 + 10 }}</div><div><input type="text" ng-model="num" /><div ng-show="num > 10">Can you see me?</div>
Directive ng-repeat
● Iterates over a collection and instantiates a new template for each item in the collection.
● Each item in the collection is given its own template and therefore its own scope.
<ul ng-controller="PeopleController"><li ng-repeat="person in people">
{{person.name}} lives in {{person.city}}</li>
</ul>
< ng-lab="1" />
● Refactor the blog page example● Iterate over posts array on the current
$scope● Toggle comments box● Enable the user to remove blog posts by
clicking on the specific link
Filters
● Provide a way to format the data we display to the user
● Angular gives us several built-in filters as well as an easy way to create our own
● Filters can be used in views, controllers and services
Usage Syntax in HTML templates
{{ name | uppercase }}
● Without params
{{ 123.4567890 | number: 2 }}{{ user.birthday | date: 'short' }}{{ posts | filter: 'animal' }}
● With params
JOHN DOE
123.467/23/15 9:59 PM[list filtered by object containing “animal”]
Usage Syntax in controllers/services
$filter("uppercase")(name);
● Without params
JOHN DOE
$filter("number")(123.4567890, 2);$filter("date")(user.birthday, "short");$filter("filter")(posts, "animal");
● With params
123.467/23/15 9:59 PM[list filtered by object containing “animal”]
Service, Provider and Factory
● Shared components● Not related to presentation layer
(only access to $rootScope)
● Singleton components● Lazy-loaded when injected the first time
Service● Syntax:angular.module("myApp", [])
.service("MyService", function MyServiceImpl() {this.hello = function() {
return "Hello World";};
});
● Result:new MyServiceImpl() is called and returned
● Usage:Can be useful for sharing utility functions to invoke by simply appending () to the injected function reference.
Factory● Syntax:angular.module("myApp", [])
.factory("MyService", function MyServiceImpl() {var privateStuff = "";return {
hello: function() { return "Hello World"; }};
});
● Result:The value returned by invoking MyServiceImpl()
● Usage:Can be useful for returning a “class” function that can then be new'ed to create instances.
Provider● Syntax:angular.module("myApp", [])
.provider("MyService", function() {var _key;return {
setApiKey: function(key) { _key = key; }$get: function() {
return {function() { return "Hello World " + _key; }
}}
};});
● Result:The value returned by invoking $get()
● Usage:Can be useful when you need to configure your service before using it (e.g.: setting an ApiKey or a baseUrl)
Config
● Allows to configure Providers before the app is bootstrapped
● Useful for setting API keys, view routes
In Java this concept is close to property files
Configangular.module("myApp", [])
.config(function(MyServiceProvider) {MyServiceProvider.setApiKey("a73841b619ef5901490dc3f42dce4c5a");
}).provider("MyService", function() {
var _key;return {
setApiKey: function(key) { _key = key; }$get: function() {
return {function() { return "Hello World " + _key; }
}}
};});
Going back to the controller definition...angular.module("myApp", [])
.controller("MainCtrl", function($scope, FooService) {
$scope.name = "John Doe";$scope.countChars = function(text) {
return FooService.countStringLength(text);}
});
angular.module("myApp", []).controller("MainCtrl", ["$scope", "FooService", function($scope, FooService) {
$scope.name = "John Doe";$scope.countChars = function(text) {
return FooService.countStringLength(text);}
}]); That notation is used to keep injection working when compressing the final javascript artifact using, for example, requirejs r.js compiler (with uglify).
Promises
● Problem:○ callback “pyramid of Doom”
step1(function (value1) {step2(value1, function(value2) {
step3(value2, function(value3) {step4(value3, function(value4) {
// Do something with value4});
});});
});
Angular $q service to the rescue!
● Let’s flatten the pyramid:
promisedStep1.then(promisedStep2).then(promisedStep3).then(promisedStep4).then(function (value4) { // Do something with value4}).catch(function (error) { // Handle any error from all above steps}).finally(function (error) { // Handle finally from all above steps});
Parallel requestsvar firstRequest = $http.get("/first.html");var secondRequest = $http.get("/second.html");
// Wait for all$q.all([firstRequest, secondRequest])
.then(function(results) {// Do something with both results
}).catch(function (error) { // Handle any error from all above steps}).finally(function (error) { // Handle finally from all above steps});
Deferred objects
var getBlogPosts = function() {var deferred = $q.defer();
$timeout(function() {deferred.resolve([{"title": "a blog post!"}];
}, 5000);
return deferred.promise;};
Can resolve or reject their promises
● Usage:Could be useful when you want to convert a callback-paradigm into a Promise philosophy!
Angular $http service
// Simple GET request example:$http.get('/someUrl')
.success(function(data, status, headers, config) { // this callback will be called asynchronously // when the response is available })
.error(function(data, status, headers, config) { // called asynchronously if an error occurs // or server returns response with an error status. });
● Reflects HTTP request methods:○ GET, POST, PUT, DELETE, HEAD
● Returns a promise with 2 extra methods:
< ng-lab="3" />
● Remove $scope.posts from MainController● Implement $http methods in BlogApi service● Load posts from BlogApi.getPosts();● Wire removePost() to BlogApi.removePost();
Multi screen navigation● Problem:
○ Multi-screen single web application○ Complex tabs, navigation○ History management (back button)○ Query params
UI Router to the rescue!angular.module("myApp", ["ui.router"])
.config(function($stateProvider) {$stateProvider.state({
name: "home",url: "/home",templateUrl: "/partials/home.html",controller: "HomeCtrl",resolve: {
BlogPosts: function(BlogApi) {return BlogApi.getPosts();
}}
});});
Injection point
<html><head>
<title>The best site ever!</title></head><body>
<div ui-view></div></body>
</html>
HTML side:
How to navigate
<a ui-sref="home">Go Home</a><a ui-sref="viewBlogPost({ id: post.id })">Show More…</a>
HTML side:
Code side:
angular.module("myApp", []).controller("MainCtrl", function($scope, $state) {
$scope.goToBlogPost = function(id) {$state.go("viewBlogPost", { id: id });
};});
< ng-lab="4" />
● Move Blog Post list template into a separate HTML fragment
● Create routes for home and addPost● Add ui-sref to navigation bar for addPost
Forms in Angular
● Controls (input, select, textarea) are ways for a user to enter data
● A Form is a collection of controls for the purpose of grouping related controls together.
● Angular provides validation services, the user can be notified of invalid input before submitting a form.
Directive ng-model
● Binds an input to the current $scope● Applies validation behaviours● Registers itself with the parent form● Works with input, select, textarea
<input type="text" ng-model="person.name" /><input type="email" ng-model="person.email" /><input type="number" ng-model="person.age" min="1" /><textarea ng-model="person.bio" ng-required="true"></textarea>
Validation in forms
● ng-model retains an internal state:○ $touched/$untouched○ $pristine/$dirty○ $valid/$invalid○ $error
● It propagates its state to the parent form:○ $pristine/$dirty○ $valid/$invalid○ $error
Validation in forms<form name="myForm" ng-submit="myForm.$valid && save(person)">
<input type="text" name="name" ng-model="person.name"ng-required="true" />
<div ng-show="myForm.name.$invalid">Name is required!</div><input type="text" name="surname" ng-model="person.surname" /><input type="email" name="email" ng-model="person.email" /><input type="number" name="age" ng-model="person.age" min="1" />
<input type="submit"ng-disabled="myForm.$invalid"value="Save User"
/></form>
< ng-lab="5" />
● Create form for adding a new blog post● Ensure validation on all fields● Call BlogApi.addPost(newPost);● Redirect to the home page ($state.go());
Custom Directivesangular.module("myApp", [])
.directive("blogPost", function() {return {
restrict: "AEC"templateUrl: "partials/blog-post.html",scope: {
showMoreLabel: "@"post: "=blogPost",canRemove: "&"
}controller: function($scope, $element) {
[...]},link: function(scope, elem, attrs, ctrls) {
[...]}
};});
● restrict○ defines when the directive could be injected:
AttributeA
<divblog-post="data"button-label="Show more..."can-remove="user.isAdmin()">
</div>
● restrict○ defines when the directive could be injected:
ElementE
<blog-postcontent="data"button-label="Show more..."can-remove="user.isAdmin()">
</blog-post>
● restrict○ defines when the directive could be injected:
CSS Class
C
<div class="blog-post"content="data"button-label="Show more..."can-remove="user.isAdmin()">
</div>
● templateUrl○ Url of the file containing an HTML fragment
representing the directive template○ It can be a function returning an Url
● template○ A string containing an HTML fragment representing
the directive template○ It can be a function returning an Url
<div><h1>{{title}}</h1><p>{{text}}</p>
</div> The template must have exactly one root element!
● scope○ false (default): it doesn’t create a new scope○ true: it creates a new child scope○ {}: it creates a new isolated scope
scope: {buttonLabel: "@"content: "=",canRemove: "&"
}
<blog-postbutton-label="Show more..."content="post.data"can-remove="user.isAdmin()">
</blog-post>
@ = &one-way binding of a string
two-way binding between scopes
expression evaluation on parent scope
< ng-lab="6" />
● Refactor the main page wrapping each blog post into a directive
● Use isolate scope to pass data into the new directive
TransclusionNesting of HTML content (also directives) inside a directiveThis mechanism allows you to grab the content of the DOM element of your directive and include it anywhere in the directive's template.
Tabs Accordion
TransclusionHow to start using it the “easy” way
1. Directive definition
angular.module("myApp", []).directive("movieCard", function() {
return {transclude: true,scope: {
title: "@"},templateUrl: "partials/movie-card.html",controller: function($scope, $element) {},link: function(scope, elem, attrs, ctrl) {}
};});
TransclusionHow to start using it the “easy” way
2. Directive template (injection point)
<div><h1>{{title}}</h1><div ng-transclude></div>
</div>
Directive-to-Directive communicationHow to create a DSL in Angular
Tabs
<tabs><tab title="Tab 1">
<div><input placeholder="Say something…"/></div><div><button>Post Comment</button>
</tab>[...]
</tabs>
Directive-to-Directive communicationHow to create a DSL in Angular
Accordion
<accordion><accordion-section title="Section 1">
<div>Quis nostrud exercitation ullamco…</div></accordion-section>[...]
</accordion>
1. Child directive definition example
angular.module("myApp", []).directive("tab", function() {
return {require: "^tabs",transclude: "true",templateUrl: "partials/tab.html",scope: {
title: "@"},link: function(scope, elem, attrs, tabsCtrl) {
tabsCtrl.registerTab(scope);}
};});
Directive-to-Directive communicationHow to create a DSL in Angular
How to use require in directives
Directive-to-Directive communicationHow to create a DSL in Angular
fooBar ?fooBar ^fooBar ?^fooBarRequire fooBar on same element and pass it to linking function
Pass fooBar controller if available on same element to linking function. If not, pass null.
Require fooBar on one of the parent elements and pass it to linking function
Pass someDirective controller if available on one of parent elements to linking function. If not, pass null
2. Child directive template
Directive-to-Directive communicationHow to create a DSL in Angular
<div ng-if="active" ng-transclude></div>
We want to show that tab only if it’s active.That’s why we are using ng-if
3. Parent directive definition example
angular.module("myApp", []).directive("tabs", function() {
return {transclude: "true"templateUrl: "partials/tabs.html",controller: function() {
this.tabs = [];this.registerTab = function(tab) {
this.tabs.push(tab);}
}};
});
Directive-to-Directive communicationHow to create a DSL in Angular
4. Parent directive template
Directive-to-Directive communicationHow to create a DSL in Angular
<ul class="tabs"><li ng-repeat="tab in ctrl.tabs">
<button ng-click="ctrl.selectTab(tab)">{{tab.title}}
</button></li>
</ul><div ng-transclude></div>
http://jsbin.com/vadalo/editThe complete example:
Suggested Teaching Materials
ng-bookThe AngularJS bible● Complete overview of the framework● Online labs
http://egghead.io/Basic to advanced video trainings● Life's too short for long boring lectures