The Art of
Matt Raible • http://raibledesigns.com
Photos by
Modern Principles in Web Development
Design for mobile first (even if you’re
not building a mobile app)

Build only single ...
Jobs on Dice.com
February 2014
500

375

250

125

© 2014 Raible Designs

ut
ko
oc
Kn

be
r
Em

lar
gu
An

Ba

ck

bo
n

e...
LinkedIn Skills
February 2014
30,000

22,500

15,000

7,500

© 2014 Raible Designs

be
r
Em

ut
ko
oc
Kn

lar
gu
An

Ba

c...
Google Trends

© 2014 Raible Designs
Indeed Job Trends

Absolute
Relative
© 2014 Raible Designs
Stack Overflow

© 2014 Raible Designs
Who wants to learn

© 2014 Raible Designs

?
The History of AngularJS
Started by Miško Hevery in 2009

GWT = 3 developers, 6 months

AngularJS = 1 developer, 3 weeks

...
The History of AngularJS
AngularJS

GWT

18000

17,000
13500
9000
4500
0

1,000

Lines of Code

© 2014 Raible Designs
Hello World
<!doctype html>
<html ng-app>
<head>
<title>Hello World</title>
</head>
<body>
<div>
<label>Name:</label>
<inp...
Architecture Principles

Boilerplate

Structure

D.R.Y.
Testability
© 2014 Raible Designs
Code Organization
Start with Angular Seed*

!

git clone https://github.com/angular/angular-seed.git

!
!
!

* more option...
App Definition
var app = angular.module('myApp', []);

<!DOCTYPE html>
<html ng-app="myApp">

© 2014 Raible Designs
App Definition with separate files
app.js

!
!

angular.module('myApp', ['ngRoute',
'myApp.filters',
'myApp.services',
'myAp...
Model View Controller

© 2014 Raible Designs
Data Binding
friend.js

!

$scope.friend = {
name: "Fernand"
};

!

friend.html

{{friend.name}}
// 1-way
<input ng-model=...
Solving FOUC
This will work just fine — if it’s not on the first page:

!

<p>{{friend.name}}</p>

Use ng-cloak or ng-bind a...
Directives
<div ng-repeat="entry in news.entries">
<span ng-bind="entry.title"></span>
<button ng-click="delete($index)">
...
Directives with valid HTML5
<div data-ng-repeat="entry in news.entries">
<span data-ng-bind="entry.title"></span>
<button ...
Custom Directives
$scope.customer = {
name: 'Franklin',
address: '1830 Blake'
};
<div ng-controller="MyController">
<my-cu...
Built-In Directives
ng-href


ng-selected


ng-src


ng-class


ng-disabled


ng-style


ng-checked


!

ng-readonly


© 2...
Services
var services = angular.module('myApp.services', ['ngResource']);
!
services.factory('LoginService', function($res...
$http
$http({method: 'GET', url: '/news'}).
success(function(data, status, headers, config) {
// this callback will be cal...
$q
myApp.factory('HelloWorld', function($q, $timeout) {
!
var getMessages = function() {
var deferred = $q.defer();
!
$tim...
$q
myApp.controller('HelloCtrl', function($scope, HelloWorld) {
!
HelloWorld.getMessages().then(function(messages) {
$scop...
Dependency Injection
.controller('LoginController', function($scope, $rootScope, $location,
$http, $cookieStore, LoginServ...
Filters
!
!
!
!

{{ name | uppercase }}
!
<!-- Displays: 123.46 -->
{{ 123.456789 | number:2 }}
!
<!-- In en-US locale, '$...
Routes
.config(['$routeProvider', '$locationProvider', '$httpProvider',
function ($routeProvider, $locationProvider, $http...
Routing: Navigation
$rootScope.logout = function () {
delete $rootScope.user;
delete $http.defaults.headers.common[xAuthTo...
Routing: Navigation
$rootScope.logout = function () {
delete $rootScope.user;
delete $http.defaults.headers.common[xAuthTo...
Code Organization Revisited
Lineman helps you build fat-client JavaScript apps

It produces happiness by building assets, ...
Google's Recommendations for Angular App Structure
Testing
Testem - test runner, framework agnostic

Jasmine - unit tests, framework agnostic

Protractor - integration tests...
Testing: Controllers
describe("controller: LoginController", function() {
!
beforeEach(function() {
module("app");
});
!
b...
Testing: Controllers
afterEach(function() {
this.$httpBackend.verifyNoOutstandingRequest();
this.$httpBackend.verifyNoOuts...
Testing: Directives
beforeEach(inject(function($rootScope, $compile) {
this.directiveMessage = 'ralph was here';
this.html...
Testing: Directives with CoffeeScript
describe "directive: shows-message-when-hovered (coffeescript)", ->
!
Given -> modul...
Testing: End-to-End
protractor = require("protractor")
require "protractor/jasminewd"
require 'jasmine-given'

!

describe...
Building with Grunt
sudo npm install
!
sudo npm install -g grunt-cli
!
vi package.json
!
"grunt": "~0.4.1",
"grunt-contrib...
Gruntfile.js
module.exports = function (grunt) {

!
!
!

!

grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
cl...
Gruntfile.js
!

useminPrepare: {
html: 'app/index.html'
},

!

usemin: {
html: ['dist/index.html']
},

!

uglify: {
options...
Gruntfile.js
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

!

// Tell Grunt what to do when we typ...
index.html comments
<head>
<title>My AngularJS App</title>
<!-- build:css css/seed.min.css -->
<link rel="stylesheet" href...
dist/index.html
<head>
<title>My AngularJS App</title>
<link rel="stylesheet" href="css/f050d0dc.seed.min.css"/>
</head>
<...
After Grunt

http://raibledesigns.com/rd/entry/using_grunt_with_angularjs_for

© 2014 Raible Designs
You shouldn’t have to worry about FEO

http://raibledesigns.com/rd/entry/you_shouldn_t_have_to
© 2014 Raible Designs
HTTP/2 Performance Anti-Patterns?
Split dominant content domains

Reduce requests

	 Merging

	 Sprites

	 DataURIs
http:/...
CloudBees

© 2014 Raible Designs
UI Bootstrap

http://angular-ui.github.io/bootstrap

<script src="lib/angular/ui-bootstrap-0.10.0.min.js"></script>
<scrip...
Ionic Framework

http://ionicframework.com

© 2014 Raible Designs
AngularJS Batarang

© 2014 Raible Designs
My Experience
Developing with AngularJS Series

Part I: The Basics

Part II: Dialogs and Data

Part III: Services

Part IV...
My Experience

http://vimeo.com/mraible/angularjs-deep-dive

#dv13javaweb$

© 2014 Raible Designs
How to Become an Artist
Part 1 of 3: Learn the Basics on Your Own

Take some time and try various mediums of art

Recogniz...
Shortcut to becoming an Angular Artist

JUST DO IT.
© 2014 Raible Designs
Questions?
Contact Me!

http://raibledesigns.com

@mraible


Presentations

http://slideshare.net/mraible


Code

http://g...
Who to follow on Twitter
AngularJS Team at Google


Web Performance


	 Miško Hevery - @mhevery


	 Ilya Grigorik - @igrig...
Resources
Angular Dart

	 https://angulardart.org 

Devoxx AngularJS Talks on Parleys.com 

http://parleys.com/play/514892...
Code
Angular Seed

https://github.com/angular/angular-seed 

Lineman Application Template using AngularJS

	 https://githu...
The Art of AngularJS
The Art of AngularJS
The Art of AngularJS
The Art of AngularJS
Upcoming SlideShare
Loading in...5
×

The Art of AngularJS

102,435

Published on

AngularJS is one of today's hottest JavaScript MVC Frameworks. In this session, we'll explore many concepts it brings to the world of client-side development: dependency injection, directives, filters, routing and two-way data binding. We'll also look at its recommended testing tools and build systems. Finally, you'll learn about my experience developing several real-world applications using AngularJS, HTML5 and Bootstrap.

Published in: Technology

The Art of AngularJS

  1. 1. The Art of Matt Raible • http://raibledesigns.com Photos by
  2. 2. Modern Principles in Web Development Design for mobile first (even if you’re not building a mobile app) Build only single page apps Create and use your own REST API “Sex sells” applies to web apps © 2014 Raible Designs
  3. 3. Jobs on Dice.com February 2014 500 375 250 125 © 2014 Raible Designs ut ko oc Kn be r Em lar gu An Ba ck bo n e 0
  4. 4. LinkedIn Skills February 2014 30,000 22,500 15,000 7,500 © 2014 Raible Designs be r Em ut ko oc Kn lar gu An Ba ck bo n e 0
  5. 5. Google Trends © 2014 Raible Designs
  6. 6. Indeed Job Trends Absolute Relative © 2014 Raible Designs
  7. 7. Stack Overflow © 2014 Raible Designs
  8. 8. Who wants to learn © 2014 Raible Designs ?
  9. 9. The History of AngularJS Started by Miško Hevery in 2009 GWT = 3 developers, 6 months AngularJS = 1 developer, 3 weeks Learn more: https://www.youtube.com/watch?v=X0VsStcCCM8 © 2014 Raible Designs
  10. 10. The History of AngularJS AngularJS GWT 18000 17,000 13500 9000 4500 0 1,000 Lines of Code © 2014 Raible Designs
  11. 11. Hello World <!doctype html> <html ng-app> <head> <title>Hello World</title> </head> <body> <div> <label>Name:</label> <input type="text" ng-model="name" placeholder="Enter a name here"> <hr> <h1>Hello {{name}}!</h1> </div> <script src="http://code.angularjs.org/1.2.13/angular.min.js"></script> </body> </html> © 2014 Raible Designs
  12. 12. Architecture Principles Boilerplate Structure D.R.Y. Testability © 2014 Raible Designs
  13. 13. Code Organization Start with Angular Seed* ! git clone https://github.com/angular/angular-seed.git ! ! ! * more options to be discussed later… © 2014 Raible Designs
  14. 14. App Definition var app = angular.module('myApp', []); <!DOCTYPE html> <html ng-app="myApp"> © 2014 Raible Designs
  15. 15. App Definition with separate files app.js ! ! angular.module('myApp', ['ngRoute', 'myApp.filters', 'myApp.services', 'myApp.directives', 'myApp.controllers' ]) controllers.js angular.module('myApp.controllers', []). controller('MyCtrl1', [function() { ! }]) © 2014 Raible Designs
  16. 16. Model View Controller © 2014 Raible Designs
  17. 17. Data Binding friend.js ! $scope.friend = { name: "Fernand" }; ! friend.html {{friend.name}} // 1-way <input ng-model="friend.name"> // 2-way © 2014 Raible Designs
  18. 18. Solving FOUC This will work just fine — if it’s not on the first page: ! <p>{{friend.name}}</p> Use ng-cloak or ng-bind attribute: <p ng-cloak>{{friend.name}}</p> ! <p ng-bind="friend.name"></p> © 2014 Raible Designs
  19. 19. Directives <div ng-repeat="entry in news.entries"> <span ng-bind="entry.title"></span> <button ng-click="delete($index)"> Delete </button> </div> © 2014 Raible Designs
  20. 20. Directives with valid HTML5 <div data-ng-repeat="entry in news.entries"> <span data-ng-bind="entry.title"></span> <button data-ng-click="delete($index)"> Delete </button> </div> <div data-ng:repeat="entry in news.entries"> <span data-ng:bind="entry.title"></span> <button data-ng:click="delete($index)"> Delete </button> </div> © 2014 Raible Designs
  21. 21. Custom Directives $scope.customer = { name: 'Franklin', address: '1830 Blake' }; <div ng-controller="MyController"> <my-customer></my-customer> </div> .directive('myCustomer', function() { return { template: 'Name: {{customer.name}} Address: {{customer.address}}' }; }); © 2014 Raible Designs
  22. 22. Built-In Directives ng-href ng-selected ng-src ng-class ng-disabled ng-style ng-checked ! ng-readonly © 2014 Raible Designs
  23. 23. Services var services = angular.module('myApp.services', ['ngResource']); ! services.factory('LoginService', function($resource) { return $resource(':action', {}, { authenticate: { method: 'POST', params: {'action': 'authenticate'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'} } } ); }); ! services.factory('NewsService', function($resource) { return $resource('news/:id', {id: '@id'}); }); © 2014 Raible Designs
  24. 24. $http $http({method: 'GET', url: '/news'}). 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. }); $http.get('/news').success(successCallback); $http.post('/news', data).success(successCallback); © 2014 Raible Designs
  25. 25. $q myApp.factory('HelloWorld', function($q, $timeout) { ! var getMessages = function() { var deferred = $q.defer(); ! $timeout(function() { deferred.resolve(['Hello', 'world!']); }, 2000); ! return deferred.promise; }; ! return { getMessages: getMessages }; }); © 2014 Raible Designs
  26. 26. $q myApp.controller('HelloCtrl', function($scope, HelloWorld) { ! HelloWorld.getMessages().then(function(messages) { $scope.messages = messages; }); ! }); © 2014 Raible Designs
  27. 27. Dependency Injection .controller('LoginController', function($scope, $rootScope, $location, $http, $cookieStore, LoginService) { $scope.login = function () { LoginService.authenticate($.param({username: $scope.username, 
 password: $scope.password}), function (user) { $rootScope.user = user; $http.defaults.headers.common[xAuthTokenHeaderName] = user.token; $cookieStore.put('user', user); $location.path("/"); }); }; }) © 2014 Raible Designs
  28. 28. Filters ! ! ! ! {{ name | uppercase }} ! <!-- Displays: 123.46 --> {{ 123.456789 | number:2 }} ! <!-- In en-US locale, '$1000.00' will be shown --> {{ 1000 | currency }} ! <!-- all of the words with e in them ["Lerner","Likes","Eat"] --> {{ ['Ari', 'Lerner', 'Likes', 'To', 'Eat', 'Pizza'] | filter:'e' }} ! also: lowercase, limitTo, orderBy © 2014 Raible Designs
  29. 29. Routes .config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) { $routeProvider.when('/create', { templateUrl: 'partials/create.html', controller: 'CreateController' }); $routeProvider.when('/edit/:id', { templateUrl: 'partials/edit.html', controller: 'EditController' }); $routeProvider.when('/login', { templateUrl: 'partials/login.html', controller: 'LoginController' }); $routeProvider.otherwise({ templateUrl: 'partials/index.html', controller: 'IndexController' }); ! $locationProvider.hashPrefix('!'); }] ) © 2014 Raible Designs
  30. 30. Routing: Navigation $rootScope.logout = function () { delete $rootScope.user; delete $http.defaults.headers.common[xAuthTokenHeaderName]; $cookieStore.remove('user'); $location.path("/login"); }; © 2014 Raible Designs
  31. 31. Routing: Navigation $rootScope.logout = function () { delete $rootScope.user; delete $http.defaults.headers.common[xAuthTokenHeaderName]; $cookieStore.remove('user'); $location.path("/login"); }; © 2014 Raible Designs
  32. 32. Code Organization Revisited Lineman helps you build fat-client JavaScript apps It produces happiness by building assets, mocking servers, and running specs on every file change git clone https://github.com/linemanjs/lineman-angular-template.git my-app cd my-app sudo npm install -g lineman npm install lineman run © 2014 Raible Designs
  33. 33. Google's Recommendations for Angular App Structure
  34. 34. Testing Testem - test runner, framework agnostic Jasmine - unit tests, framework agnostic Protractor - integration tests, angular specific Lineman - productivity, framework agnostic © 2014 Raible Designs
  35. 35. Testing: Controllers describe("controller: LoginController", function() { ! beforeEach(function() { module("app"); }); ! beforeEach(inject(function($controller, $rootScope, $location, AuthenticationService, $httpBackend) { this.$location = $location; this.$httpBackend = $httpBackend; this.scope = $rootScope.$new(); this.redirect = spyOn($location, 'path'); $controller('LoginController', { $scope: this.scope, $location: $location, AuthenticationService: AuthenticationService }); })); © 2014 Raible Designs
  36. 36. Testing: Controllers afterEach(function() { this.$httpBackend.verifyNoOutstandingRequest(); this.$httpBackend.verifyNoOutstandingExpectation(); }); ! describe("successfully logging in", function() { it("should redirect you to /home", function() { this.$httpBackend.expectPOST('/login', this.scope.credentials).respond(200); this.scope.login(); this.$httpBackend.flush(); expect(this.redirect).toHaveBeenCalledWith('/home'); }); }); }); © 2014 Raible Designs
  37. 37. Testing: Directives beforeEach(inject(function($rootScope, $compile) { this.directiveMessage = 'ralph was here'; this.html = "<div shows-message-when-hovered message='" + this.directiveMessage + "'></div>"; this.scope = $rootScope.$new(); this.scope.message = this.originalMessage = 'things are looking grim'; this.elem = $compile(this.html)(this.scope); })); ! describe("when a user mouses over the element", function() { it("sets the message on the scope to the message attribute", function() { this.elem.triggerHandler('mouseenter'); expect(this.scope.message).toBe(this.directiveMessage); }); }); © 2014 Raible Designs
  38. 38. Testing: Directives with CoffeeScript describe "directive: shows-message-when-hovered (coffeescript)", -> ! Given -> module("app") ! Given inject ($rootScope, $compile) -> @directiveMessage = 'ralph was here' @html = "<div shows-message-when-hovered message='#{@directiveMessage}'></div>" @scope = $rootScope.$new() @scope.message = @originalMessage = 'things are looking grim' @elem = $compile(@html)(@scope) ! describe "when a user mouses over the element", -> When -> @elem.triggerHandler('mouseenter') Then "the message on the scope is set to the message attribute", -> @scope.message == @directiveMessage © 2014 Raible Designs
  39. 39. Testing: End-to-End protractor = require("protractor") require "protractor/jasminewd" require 'jasmine-given' ! describe "my angular app", -> ptor = protractor.getInstance() describe "visiting the login page", -> Given -> ptor.get "/" ! describe "when a user logs in", -> Given -> ptor.findElement(protractor.By.input("credentials.username")).sendKeys "Ralph" Given -> ptor.findElement(protractor.By.input("credentials.password")).sendKeys "Wiggum" When -> ptor.findElement(protractor.By.id("log-in")).click() Then -> ptor.findElement(protractor.By.binding("{{ message }}")).getText().then (text) -> expect(text).toEqual "Mouse Over these images to see a directive at work" © 2014 Raible Designs
  40. 40. Building with Grunt sudo npm install ! sudo npm install -g grunt-cli ! vi package.json ! "grunt": "~0.4.1", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-uglify": "~0.2.7", "grunt-contrib-cssmin": "~0.7.0", "grunt-usemin": "~2.0.2", "grunt-contrib-copy": "~0.5.0", "grunt-rev": "~0.1.0", "grunt-contrib-clean": "~0.5.0", "matchdep": "~0.3.0" © 2014 Raible Designs
  41. 41. Gruntfile.js module.exports = function (grunt) { ! ! ! ! grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), clean: ["dist", '.tmp'], copy: { main: { expand: true, cwd: 'app/', src: ['**', '!js/**', '!lib/**', '!**/*.css'], dest: 'dist/' } }, rev: { files: { src: ['dist/**/*.{js,css}'] } }, © 2014 Raible Designs
  42. 42. Gruntfile.js ! useminPrepare: { html: 'app/index.html' }, ! usemin: { html: ['dist/index.html'] }, ! uglify: { options: { report: 'min', mangle: false } } }); ! © 2014 Raible Designs
  43. 43. Gruntfile.js require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); ! // Tell Grunt what to do when we type "grunt" into the terminal grunt.registerTask('default', [ 'copy', 'useminPrepare', 'concat', 'uglify', 'cssmin', 'rev', 'usemin' ]); }; © 2014 Raible Designs
  44. 44. index.html comments <head> <title>My AngularJS App</title> <!-- build:css css/seed.min.css --> <link rel="stylesheet" href="css/app.css"/> <link rel="stylesheet" href="css/app2.css"/> <!-- endbuild --> </head> <body> <!-- build:js js/seed.min.js --> <script src="lib/angular/angular.js"></script> <script src="lib/angular/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> <script src="js/filters.js"></script> <script src="js/directives.js"></script> <!-- endbuild --> </body> © 2014 Raible Designs
  45. 45. dist/index.html <head> <title>My AngularJS App</title> <link rel="stylesheet" href="css/f050d0dc.seed.min.css"/> </head> <body> ! <script src="js/8973cf0f.seed.min.js"></script> </body> © 2014 Raible Designs
  46. 46. After Grunt http://raibledesigns.com/rd/entry/using_grunt_with_angularjs_for © 2014 Raible Designs
  47. 47. You shouldn’t have to worry about FEO http://raibledesigns.com/rd/entry/you_shouldn_t_have_to © 2014 Raible Designs
  48. 48. HTTP/2 Performance Anti-Patterns? Split dominant content domains Reduce requests Merging Sprites DataURIs http://www.slideshare.net/andydavies © 2014 Raible Designs
  49. 49. CloudBees © 2014 Raible Designs
  50. 50. UI Bootstrap http://angular-ui.github.io/bootstrap <script src="lib/angular/ui-bootstrap-0.10.0.min.js"></script> <script src="lib/angular/ui-bootstrap-tpls-0.10.0.min.js"></script> angular.module('myApp', ['ui.bootstrap']); © 2014 Raible Designs
  51. 51. Ionic Framework http://ionicframework.com © 2014 Raible Designs
  52. 52. AngularJS Batarang © 2014 Raible Designs
  53. 53. My Experience Developing with AngularJS Series Part I: The Basics
 Part II: Dialogs and Data
 Part III: Services
 Part IV: Making it Pop
 © 2014 Raible Designs
  54. 54. My Experience http://vimeo.com/mraible/angularjs-deep-dive #dv13javaweb$ © 2014 Raible Designs
  55. 55. How to Become an Artist Part 1 of 3: Learn the Basics on Your Own Take some time and try various mediums of art Recognize your strengths Do your research and learn the basics Get the supplies you will need Observe the world around you Make time for your art every day Seek out the opinions of others Develop your own style http://www.wikihow.com/Become-an-Artist © 2014 Raible Designs
  56. 56. Shortcut to becoming an Angular Artist JUST DO IT. © 2014 Raible Designs
  57. 57. Questions? Contact Me! http://raibledesigns.com @mraible Presentations http://slideshare.net/mraible Code http://github.com/mraible © 2014 Raible Designs
  58. 58. Who to follow on Twitter AngularJS Team at Google Web Performance Miško Hevery - @mhevery Ilya Grigorik - @igrigorik Igor Minar - @IgorMinar Andy Davis - @andydavies Brian Ford - @briantford Steve Souders - @Souders ! © 2014 Raible Designs
  59. 59. Resources Angular Dart https://angulardart.org Devoxx AngularJS Talks on Parleys.com http://parleys.com/play/5148922b0364bc17fc56c91b (2012) http://parleys.com/play/529321a5e4b054cd7d2ef4e1 (2013) Egghead.io - https://egghead.io/ © 2014 Raible Designs
  60. 60. Code Angular Seed https://github.com/angular/angular-seed Lineman Application Template using AngularJS https://github.com/linemanjs/lineman-angular-template AngularJS + Rest + Spring Security https://github.com/joshlong/boot-examples/tree/master/x-auth-security © 2014 Raible Designs
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×