Upload
chris-richardson
View
2.913
Download
1
Embed Size (px)
DESCRIPTION
A quick introduction to AngularJS
Citation preview
@crichardson
Introduction to AngularJS
Chris Richardson
Author of POJOs in ActionFounder of the original CloudFoundry.com
@[email protected] http://plainoldobjects.com
@crichardson
Agenda
Introduction to AngularJS
Anatomy of an AngularJS application
Automated testing
Development tools
@crichardson
Browser
WAR
StoreFrontUI
Model
View Controller
Presentation layer evolution....
HTML / HTTP
+ JavaScript
@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
@crichardson
MV* frameworks for the browser
@crichardson
@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
@crichardson
Hello world
@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
@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>
@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/#/
@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
@crichardson
Agenda
Introduction to AngularJS
Anatomy of an AngularJS application
Automated testing
Development tools
@crichardson
Food to Go
@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: '/'}); });
@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
@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(); );
@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>
@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
@crichardson
AvailableRestaurants resource
angular.module('orderTakingServices', ['ngResource']). factory('AvailableRestaurants', function($resource) { return $resource('/app/availablerestaurants'); });
CRUD methods ⇒ RESTful request
@crichardson
Available restaurants
@crichardson
Displaying available - route and controller
orderTakingModule.controller('DisplayAvailableCtrl', function ($scope, OrderState) { $scope.orderState = OrderState; });
when('/displayavailable', {controller: 'DisplayAvailableCtrl', templateUrl: 'views/displayavailable.html'})
@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
@crichardson
Creating an order
@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
@crichardson
Restaurants.js
angular.module('orderTakingServices', ...). factory('Restaurant',function ($resource) { return $resource('/app/restaurant/:id'); });
CRUD methods ⇒ RESTful request
@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
...
@crichardson
Agenda
Introduction to AngularJS
Anatomy of an AngularJS application
Automated testing
Development tools
@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
@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
@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
@crichardson
Agenda
Introduction to AngularJS
Anatomy of an AngularJS application
Automated testing
Development tools
@crichardson
Scaffold Build, preview,
testPackage manager
@crichardson
$ yo angular:app
Generates skeleton application
@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" }}
@crichardson
$ grunt serverLaunches application in browser with live
reloading
@crichardson
$ grunt test
Runs tests using the Karma test runner
@crichardson
$ grunt build
Packages application: runs jshint, minification, concatenation, ...
@crichardson
Summary
MVC has migrated to where it belongs: the browser
AngularJS looks like pretty cool framework
Yeoman and friends look promising
@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