41
@crichardson Introduction to AngularJS Chris Richardson Author of POJOs in Action Founder of the original CloudFoundry.com @crichardson [email protected] http://plainoldobjects.com

Introduction to AngularJS (@oakjug June 2013)

Embed Size (px)

DESCRIPTION

A quick introduction to AngularJS

Citation preview

Page 1: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Introduction to AngularJS

Chris Richardson

Author of POJOs in ActionFounder of the original CloudFoundry.com

@[email protected] http://plainoldobjects.com

Page 2: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Agenda

Introduction to AngularJS

Anatomy of an AngularJS application

Automated testing

Development tools

Page 3: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Browser

WAR

StoreFrontUI

Model

View Controller

Presentation layer evolution....

HTML / HTTP

+ JavaScript

Page 4: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Browser Web application

RESTfulEndpointsModel

View Controller

...Presentation layer evolution

JSON-REST

HTML 5 - JavaScript

No elaborate, server-side web framework required

Event publisher

Events

Static content

Page 5: Introduction to AngularJS (@oakjug June 2013)

@crichardson

MV* frameworks for the browser

Page 6: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Page 7: Introduction to AngularJS (@oakjug June 2013)

@crichardson

AngularJS style MVCTemplate Controller Model

<html> <body ng-app="..."> <h1>Hello {{name}}</h1> <form ng-submit="enterName()" > <input id="firstName" ng-model="firstName" >

function MainCtrl ($scope) { ... $scope.enterName = function () { $scope.name = $scope.firstName + ' ' + $scope.lastName; };

MainCtrl Scope

enterName: FunctionfirstName: ...lastName: ...name: ...

View

Page 8: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Hello world

Page 9: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Hello world - index.html

<body ng-app="helloworldApp">

<div class="container" ng-view></div>

<script src="components/angular/angular.js"></script> <script src="components/angular-resource/angular-resource.js"></script> <script src="components/angular-cookies/angular-cookies.js"></script> <script src="components/angular-sanitize/angular-sanitize.js"></script>

<script src="scripts/app.js"></script> <script src="scripts/controllers/main.js"></script>

</body>

Application’s module and root element

Where to render template

Page 10: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Hello world - main.html<div class="hero-unit"> <h1 ng-show="name">Hello {{name}}</h1>

<form name="nameForm" ng-submit="enterName()"> <label for="firstName">First Name:</label> <input id="firstName" type="text" name="firstName" size="30" placeholder="enter your first name" required ng-model="firstName" > <label for="lastName">Last Name:</label> <input id="lastName" type="text" name="lastName" size="30" placeholder="enter your last name" required ng-model="lastName" > <input id="enterNameButton" class="btn-primary" type="submit" value="Enter" ng-disabled="nameForm.$invalid"> </form>

</div>

Page 11: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Hello world - app.js

angular.module('helloworldApp', []) .config(function ($routeProvider) { $routeProvider .when('/', { templateUrl: 'views/main.html', controller: 'MainCtrl' }) .otherwise({ redirectTo: '/' }); });

defines the routes

URL ⇒view + controller

creates module with dependencies

Dependency injection

http://localhost:9000/#/

Page 12: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Hello world - controllers.js

angular.module('helloworldApp') .controller('MainCtrl', function ($scope) { $scope.enterName = function () { $scope.name = $scope.firstName + ' ' + $scope.lastName; }; });

handle form submission

Populate the $scope with data and callbacksretrieves module

Page 13: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Agenda

Introduction to AngularJS

Anatomy of an AngularJS application

Automated testing

Development tools

Page 14: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Food to Go

Page 15: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Module and route definitions - app.js

var orderTakingModule = angular.module('ordertaking', ['orderTakingServices', 'orderstate', 'deliveryinfoutils']). config(function ($routeProvider) { $routeProvider. when('/', {controller: 'DeliveryInfoCtrl', templateUrl: 'views/enterdeliveryinformation.html'}). when('/displayavailable', {controller: 'DisplayAvailableCtrl', templateUrl: 'views/displayavailable.html'}). when('/selectrestaurant/:id', {controller: 'SelectRestaurantCtrl', templateUrl: 'views/displaymenu.html'}). when('/ordersummary', {controller: 'DisplayOrderSummaryCtrl', templateUrl: 'views/ordersummary.html'}). when('/orderfinalsummary', {controller: 'PlaceOrderCtrl', templateUrl: 'views/orderfinalsummary.html'}). when('/orderconfirmation', {controller: 'DisplayOrderConfirmationCtl', templateUrl: 'views/orderconfirmation.html'}). when('/aboutus', {templateUrl: 'views/aboutus.html'}). when('/help', {templateUrl: 'views/help.html'}). when('/howitworks', {templateUrl: 'views/howitworks.html'}). otherwise({redirectTo: '/'}); });

Page 16: Introduction to AngularJS (@oakjug June 2013)

@crichardson

controller - DeliveryInfoCtrlorderTakingModule.controller('DeliveryInfoCtrl', function ($scope, $location, AvailableRestaurants, OrderState, DeliveryInfoUtils) {

$scope.orderState = OrderState;

$scope.deliveryHours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; $scope.deliveryMinutes = [0, 15, 30, 45];

var now = new Date(); $scope.currentTime = now.getHours() * 100 + now.getMinutes();

$scope.isNotInFuture = function () { return DeliveryInfoUtils.isDeliveryTimeNotInFuture($scope.deliveryTime); }

$scope.showAvailableRestaurants = function () { .... }; });

the “cart”

for selectors

validation

form submission

Page 17: Introduction to AngularJS (@oakjug June 2013)

@crichardson

“Shopping cart” - orderstate.coffeeangular.module('orderstate', []). factory('OrderState', -> class OrderState

menuItemsMaybe: -> @selectedRestaurant?.menuItems || []

getMenuItemQuantities: -> mi.quantity for mi in @menuItemsMaybe()

getSelectedMenuItems: -> mi for mi in @menuItemsMaybe() when mi.quantity > 0 noteUpdatedMenuItemQuantities: -> @selectedMenuItems = @getSelectedMenuItems() @totalCost = 0 for mi in @selectedMenuItems @totalCost = @totalCost + mi.quantity * mi.price

makeOrder: -> deliveryInfo: @deliveryInfo restaurantId: @selectedRestaurant.id orderLineItems: {name: mi.name, quantity: mi.quantity} for mi in @selectedMenuItems return new OrderState(); );

Page 18: Introduction to AngularJS (@oakjug June 2013)

@crichardson

View template - enterdeliveryinfo.html

<form name="deliveryInfoForm" ng-submit="showAvailableRestaurants()"> <label for="zipCode">Zip code:</label> <input type="text" name="zipCode" ng-model="deliveryZipCode" size="5" placeholder="enter your zip code" required ng-pattern="/^[0-9]{5,5}$/"> <label for="deliveryTimeHour">Delivery Time:</label> <select ng-model="deliveryTime.hour" ng-options="i for i in deliveryHours" required style="width: 4em"> </select> <select ng-model="deliveryTime.minute" ng-options="i for i in deliveryMinutes" required style="width: 4em"> </select> <select ng-model="deliveryTime.ampm" required style="width: 4em"> <option value="am">AM</option> <option value="pm">PM</option> </select> <span class="error" ng-show="isNotInFuture()">Please pick a time in the future</span> <br/> <input id="enterDeliveryInfoButton" class="btn-primary" type="submit" value="Next" ng-disabled="isNotInFuture() || deliveryInfoForm.$invalid"></form>

Page 19: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Fetching available restaurants

orderTakingModule.controller('DeliveryInfoCtrl', function ($scope, $location, AvailableRestaurants, OrderState, DeliveryInfoUtils) {

... $scope.showAvailableRestaurants = function () { var deliveryInfo = DeliveryInfoUtils.makeDeliveryInfo($scope.deliveryTime, $scope.deliveryZipCode);

OrderState.deliveryInfo = deliveryInfo;

AvailableRestaurants.get({zipcode: deliveryInfo.address.zipcode, dayOfWeek: deliveryInfo.time.dayOfWeek, hour: deliveryInfo.time.hour, minute: deliveryInfo.time.minute}, function (ars) { OrderState.availableRestaurants = ars.availableRestaurants; $location.path('/displayavailable'); }); }; });

update cart

query server

update cartchange view

Page 20: Introduction to AngularJS (@oakjug June 2013)

@crichardson

AvailableRestaurants resource

angular.module('orderTakingServices', ['ngResource']). factory('AvailableRestaurants', function($resource) { return $resource('/app/availablerestaurants'); });

CRUD methods ⇒ RESTful request

Page 21: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Available restaurants

Page 22: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Displaying available - route and controller

orderTakingModule.controller('DisplayAvailableCtrl', function ($scope, OrderState) { $scope.orderState = OrderState; });

when('/displayavailable', {controller: 'DisplayAvailableCtrl', templateUrl: 'views/displayavailable.html'})

Page 23: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Displaying available - view

<span>Delivering to {{orderState.deliveryInfo.address.zipcode}} at {{orderState.deliveryInfo.timeOfDay}}</span><table id="availableRestaurantsTable"> <tr ng-repeat="r in orderState.availableRestaurants"> <td>{{r.name}}</td> <td> <a href="#/selectrestaurant/{{r.id}}">select</a> </td> </tr></table>

iterate through restaurants

select restaurant

Page 24: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Creating an order

Page 25: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Getting restaurant details

orderTakingModule.controller('SelectRestaurantCtrl', function SelectRestaurantCtrl($scope, $routeParams, OrderState, Restaurant, $location) {

$scope.orderState = OrderState;

OrderState.selectedRestaurant = Restaurant.get({id: $routeParams.id});

$scope.$watch("orderState.getMenuItemQuantities()", OrderState.noteUpdatedMenuItemQuantities.bind(OrderState), true);

$scope.displayOrderSummary = function () { $location.path('/ordersummary'); } });

Get restaurant from server

call this method

when this changes

Contains values from URL

Page 26: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Restaurants.js

angular.module('orderTakingServices', ...). factory('Restaurant',function ($resource) { return $resource('/app/restaurant/:id'); });

CRUD methods ⇒ RESTful request

Page 27: Introduction to AngularJS (@oakjug June 2013)

@crichardson

But wait, there’s more!Directives

Define custom DOM attributes (or elements)

Behavior or DOM transformation

e.g. use JQuery UI widgets

Filters for data formatting in the view

Using $scope.$apply() to execute an expression in angular from outside it’s event loop

...

Page 28: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Agenda

Introduction to AngularJS

Anatomy of an AngularJS application

Automated testing

Development tools

Page 29: Introduction to AngularJS (@oakjug June 2013)

@crichardson

AngularJS promotes testability

Dependency injection simplifies unit testing

MV* = Separation of concerns ⇒ improves testability

AngularJS provides

mocks for unit testing

Angular Scenario Runner for end to end testing

Page 30: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Jasmine unit testdescribe('DeliveryInfoCtrl', function () {

beforeEach(inject(function ($controller) { ctrl = $controller('DeliveryInfoCtrl', {$scope: scope, $location: location}); }));

describe('showAvailableRestaurants', function () {

var availableRestaurants = {...};

it('should fetch available restaurants', function () { $httpBackend.expectGET('/app/availablerestaurants?dayOfWeek=' + dayOfWeek + '&hour=18&minute=15&zipcode=94619'). respond(availableRestaurants);

scope.deliveryTime = {hour: 6, minute: 15, ampm: "pm"}; scope.deliveryZipCode = "94619";

scope.showAvailableRestaurants();

$httpBackend.flush();

expect(location.newPath).toBe('/displayavailable'); expect(theOrderState.availableRestaurants).toEqual(availableRestaurants.availableRestaurants); }); });

Mock provided by AngularJS

Override dependencies

Setup form values

Process async requests

Page 31: Introduction to AngularJS (@oakjug June 2013)

@crichardson

E2E testing with the Angular Scenario Runner

describe('Order taking application', function() {

it('should work', function() { browser().navigateTo('/'); expect(browser().location().url()).toBe('/'); input('deliveryZipCode').enter('94619'); select('deliveryTime.hour').option(6 - 1); select('deliveryTime.minute').option("45"); select('deliveryTime.ampm').option("pm"); element("#enterDeliveryInfoButton", "enterDeliveryInfoButton").click();

element("#availableRestaurantsTable a:first", "select available restaurant").click();

using("#menuTable tr:first", "mi quantity").input("mi.quantity").enter("3"); expect(element("#orderTotal").text()).toBe("297"); .... }); });

“Selenium-style” testing framework

Page 32: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Agenda

Introduction to AngularJS

Anatomy of an AngularJS application

Automated testing

Development tools

Page 33: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Scaffold Build, preview,

testPackage manager

Page 34: Introduction to AngularJS (@oakjug June 2013)

@crichardson

$ yo angular:app

Generates skeleton application

Page 35: Introduction to AngularJS (@oakjug June 2013)

@crichardson

$ bower install [package]

Installs JavaScript libraries

{ "name": "helloworld", "version": "0.0.0", "dependencies": { "angular": "~1.0.5", "json3": "~3.2.4", "es5-shim": "~2.0.8", "angular-resource": "~1.0.5", "angular-cookies": "~1.0.5", "angular-sanitize": "~1.0.5" }, "devDependencies": { "angular-mocks": "~1.0.5", "angular-scenario": "~1.0.5" }}

Page 36: Introduction to AngularJS (@oakjug June 2013)

@crichardson

$ grunt serverLaunches application in browser with live

reloading

Page 37: Introduction to AngularJS (@oakjug June 2013)

@crichardson

$ grunt test

Runs tests using the Karma test runner

Page 38: Introduction to AngularJS (@oakjug June 2013)

@crichardson

$ grunt build

Packages application: runs jshint, minification, concatenation, ...

Page 39: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Summary

MVC has migrated to where it belongs: the browser

AngularJS looks like pretty cool framework

Yeoman and friends look promising

Page 40: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Questions?

@crichardson [email protected]

http://plainoldobjects.com - code and slides

Page 41: Introduction to AngularJS (@oakjug June 2013)

@crichardson

Linkshttp://todomvc.com/

http://angularjs.org/

http://pivotal.github.io/jasmine/

http://yeoman.io/

http://karma-runner.github.io/0.8/index.html

http://rmurphey.com/blog/2012/04/12/a-baseline-for-front-end-developers/

https://github.com/cer/polyglot-restaurant