angularjs workshop

86
AngularJS Workshop Gianluca Cacace [email protected]

Upload: gianluca-cacace

Post on 16-Aug-2015

84 views

Category:

Engineering


4 download

TRANSCRIPT

AngularJSWorkshop

Gianluca [email protected]

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

Let’s go back in time

What is AngularJS?

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');

Traditional Web Application

Client Server

GET /blog

GET /blog

Page refresh

● 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!" }]

So, what is AngularJS?

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)

Why AngularJS?

How an Angular Appis made

View

HTML

CSS

Controller

Javascript

API

Model

User Server

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 Hierarchy

$rootScope

$scope $scope

$scope

$$nextSibling

$parent $parent

$parent

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”]

< ng-lab="2" />

● Add date filter to each blog post● Enable filtering blog posts by freetext

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; }

}}

};});

So, how to use them?

Dependency Injection!

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

CSS Class

CElement

E

● 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

ng-thanks

Gianluca [email protected]