AngularJS Services

Design, build, and test services to create a solid foundation for your AngularJS applications


Jim Lavin


153 Pages

18535 Reads

43 Downloads

English

PDF Format

3.00 MB

Java Script

Download PDF format


  • Jim Lavin   
  • 153 Pages   
  • 25 Feb 2015
  • Page - 1

    read more..

  • Page - 2

    AngularJS Services Design, build, and test services to create a solid foundation for your AngularJS applications Jim Lavin BIRMINGHAM - MUMBAI read more..

  • Page - 3

    AngularJS Services Copyright © 2014 Packt Publishing All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this read more..

  • Page - 4

    Credits Author Jim Lavin Reviewers Ruy Adorno Mike McElroy JD Smith Acquisition Editor Joanne Fitzpatrick Content Development Editor Anila Vincent Technical Editors Pankaj Kadam Aman Preet Singh Copy Editors Janbal Dharmaraj Karuna Narayanan Alfida Paiva Project Coordinator Neha Bhatnagar Proofreaders Maria Gould Ameesha Green Indexer Tejal Soni Production Coordinators Melwyn D'sa Alwin Roy Cover Work Melwyn D'sa read more..

  • Page - 5

    About the Author Jim Lavin has been involved in software development for the past 30 years. He is currently the CTO for one of the leading service providers of online ordering for restaurants where his team uses AngularJS as a core part of their service offerings. He is the coordinator of the Dallas/Fort Worth area's AngularJS meetup group, and routinely gives read more..

  • Page - 6

    About the Reviewers Ruy Adorno is a senior front-end developer with more than 10 years of experience working in web development, application interfaces, and user experience. You can get to know more about him on his personal website http://ruyadorno.com . Mike McElroy is a longtime fan, booster, and contributor to the AngularJS community. He originally met the author through read more..

  • Page - 7

    www.PacktPub.com Support files, eBooks, discount offers, and more You might want to visit www.PacktPub.com for support files and downloads related to your book. Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a print book customer, you are entitled to a read more..

  • Page - 8

    Table of Contents Preface 1 Chapter 1: The Need for Services 7 AngularJS best practices 8 Responsibilities of controllers 8 Responsibilities of directives 9 Responsibilities of services 10 Summary 11 Chapter 2: Designing Services 13 Measure twice, and cut once 13 Defining your service's interface 13 Focus on the developer, not yourself 14 Favor readability over brevity read more..

  • Page - 9

    Table of Contents [ ii ] Chapter 3: Testing Services 33 The basics of a test scenario 33 Loading your modules in a scenario 35 Mocking data 36 Mocking services 38 Mocking services with Jasmine spies 40 Handling dependencies that return promises 42 Mocking backend communications 45 Mocking timers 47 Summary 48 Chapter 4: Handling Cross-cutting Concerns 49 Communicating read more..

  • Page - 10

    Table of Contents [ iii ] Displaying data from external services 128 Building and calculating the recipe 130 Messaging is the heart of the application 132 Summary 132 Index 135 read more..

  • Page - 11

    read more..

  • Page - 12

    Preface AngularJS is a popular JavaScript framework for building single-page applications that has taken the open source community by storm since it reached the spotlight in 2013. Since then, AngularJS has seen an exponential growth in its adoption. With this adoption, new modules are released almost daily that integrate popular libraries such as Bootstrap, D3.js, and Cordova into read more..

  • Page - 13

    Preface [ 2 ] What this book covers Chapter 1, The Need for Services, discusses why services provide the core foundation in AngularJS applications and what should and shouldn't go into a service. The chapter also covers AngularJS best practices and how to properly partition an application's functionality across the various components of AngularJS. Chapter 2, Designing Services, read more..

  • Page - 14

    Preface [ 3 ] What you need for this book This book is about writing AngularJS applications and as such, you'll want to have a computer to look through the sample application and run it. Since the sample application uses Grunt.js to build, run test suites, and run the application, you'll also need Node.js installed on your computer. I would also recommend having a good read more..

  • Page - 15

    Preface [ 4 ] // go here }); app.controller('ChildCtrl', function($scope, $controller) { // this call to $controller adds the ParentCtrl's methods // and properties to the ChildCtrl's scope $controller('ParentCtrl', {$scope: $scope}); // ChildCtrl's methods and properties go here }); Any command-line input or output is written as follows: bower install npm install New read more..

  • Page - 16

    Preface [ 5 ] Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com . If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. Errata Although we have taken every care to ensure the accuracy of our read more..

  • Page - 17

    read more..

  • Page - 18

    The Need for Services In this chapter, we are going to cover why services are needed, how they support your application, and their responsibility in the overall application. Along the way, we will cover the responsibilities of the other AngularJS components and provide some guidelines on how to determine where your code should go according to the best practices. I've watched read more..

  • Page - 19

    The Need for Services [ 8 ] AngularJS best practices One of the most important things to know when picking up a new framework is how to use it correctly, and that is where best practices come in. Best practices are guidelines on how best to use a framework. They also provide guidelines on how to structure your application to make it maintainable, testable, and read more..

  • Page - 20

    Chapter 1 [ 9 ] You will often find yourself repeating the same event handling code over and over in your controllers and may be tempted to move that code to a service, but it is sometimes better to move the code to a base controller. You can then inherit that functionality by using the $controller service to add the base controller's properties and functions on read more..

  • Page - 21

    The Need for Services [ 10 ] Best practices also state that you should move repetitive HTML code to a directive to help simplify the HTML code across your application: <div class="row-fluid" ng-repeat="adjunct in adjuncts"> <div class="span1">{{adjunct.Name}}</div> <div read more..

  • Page - 22

    Chapter 1 [ 11 ] Services should also be used to integrate with external services that you use in your application. For example, you could create a service that wraps the Dropbox API to allow your application to write its data to a user's Dropbox account or encapsulate an analytics library to keep track of how users navigate through your application. Since AngularJS read more..

  • Page - 23

    read more..

  • Page - 24

    Designing Services Before you start coding your service, it is best to spend some time thinking about what functionality it provides, how it provides that functionality, and how to structure it for testability and maintainability. Measure twice, and cut once There is an old saying, "Measure twice, and cut once". This saying came about as a best practice to not waste building materials. read more..

  • Page - 25

    Designing Services [ 14 ] Focus on the developer, not yourself When you start to define a service, think about the consumer of your service and what their skill levels are; are they experienced developers, are they new to the framework, or are they in-between? Does your service require knowledge of higher-level concepts to comprehend? Or, is it very basic to read more..

  • Page - 26

    Chapter 2 [ 15 ] Keep method naming consistent Keep consistency across all of your method names to make your service interface easier to consume. If each of your services uses different naming methods, the developer trying to consume your service is going to loath your existence for all eternity. If one method that retrieves fermentable ingredients for a recipe is called read more..

  • Page - 27

    Designing Services [ 16 ] These prompts help developers to understand how to call your service, what parameters need to be passed into a method, and what type the parameters need to be. These prompts make consuming a service much easier and help developers avoid having to keep looking at documentation while they are coding. Now that we've covered some core guidelines to read more..

  • Page - 28

    Chapter 2 [ 17 ] However, if you follow the Law of Demeter, you can simplify your code, as follows: $scope.getBrewerName = function (brewer) { if (brewer) { return brewer.firstName + ' ' + brewer.lastName; } return ''; }, Now, the code is only validating the object it invokes methods on, not on the wrapper object that may read more..

  • Page - 29

    Designing Services [ 18 ] window: $injector.get('$window'), shaService: $injector.get('sha1'), userResource: $injector.get('userResource'), currentBrewer: null, // methods login: function (username, password) { authenticate.userResource.query({UserName: username}) .then(function (brewers) { read more..

  • Page - 30

    Chapter 2 [ 19 ] }; return authenticate; }); The following second service definition passes the service's dependencies in the factory method, which makes it much clearer to understand. Also, since the dependencies are passed in the factory method, the code is less verbose since you do not have to prefix each of the service instances when using them. read more..

  • Page - 31

    Designing Services [ 20 ] return false; }, logout: function () { authenticate.currentBrewer = null; $rootScope.$broadcast('USER_UPDATED'); } }; return authenticate; }); Again, leave using the $injector service to those occasions when you cannot pass in the dependencies to read more..

  • Page - 32

    Chapter 2 [ 21 ] Also, by encapsulating calls to external services in a method on your service, you can test that piece of code easily and with as little code as possible. Let's face it, we programmers are lazy when it comes to writing code and the less we have to write, the easier our lives seem. Also, when it comes down to writing unit tests for a service, read more..

  • Page - 33

    Designing Services [ 22 ] Services, factories, and providers As we have discussed so far, a service is a single instance of an object, function, or value that you can leverage across the various components of your application. When you inject a service into an application, the $inject service first looks to check if an instance of the service already exists. If it read more..

  • Page - 34

    Chapter 2 [ 23 ] _LOG_INFO_: '_LOG_INFO_', _LOG_WARN_: '_LOG_WARN_', _LOG_ERROR_: '_LOG_ERROR_', _LOG_FATAL_: '_LOG_FATAL_', } }); Why would you use a constant method over a value method? The main consideration is whether you need to allow the read more..

  • Page - 35

    Designing Services [ 24 ] Brewer.prototype = { fullName: function(){ return this.firstName + ' ' + this.lastName; }, hasItemInInventory: function(value){ var result = false; if (this.inventory && this.inventory.length > 0){ angular.forEach(this.inventory, function(item){ if(item.name read more..

  • Page - 36

    Chapter 2 [ 25 ] (function () { 'use strict'; var Logging = function () { this.log = null; }; Logging.prototype = { init: function () { // get the logger object this.log = log4javascript.getLogger("main"); // set the log level this.log.setLevel(log4javascript.Level.ALL); // read more..

  • Page - 37

    Designing Services [ 26 ] The factory method wraps the constructor function with a default provider, which in turn is used to return the service instance when the provider's $get method is called. In the following code, we've defined the same logging service, but this time, we've used the factory method to define an object instance and return it at the end of read more..

  • Page - 38

    Chapter 2 [ 27 ] The final way we can define a service is to use the module's provider method. The provider method registers a service provider that has a $get method that instantiates the service. The definition of the service provider can also have additional methods that can be called when you reference the provider itself and not the instance. If you need read more..

  • Page - 39

    Designing Services [ 28 ] } function fatal(message) { logging.log.fatal(message); } return { trace: trace, debug: debug, info: info, warn: warn, error: error, read more..

  • Page - 40

    Chapter 2 [ 29 ] The easiest way to prevent polluting the global namespace is to create an Immediately-Invoked Function Expression (IIFE) that you then use to define your module and service: (function () { 'use strict'; // module definition goes here // service definition goes here })(); Not only can this pattern be used for your services, but you can also read more..

  • Page - 41

    Designing Services [ 30 ] //#region Internal Properties var cache = {}; //#endregion //#region Internal Methods function publish(topic, args) { cache[topic] && angular.forEach(cache[topic], function (callback) { callback.apply(null, args || []); }); } function read more..

  • Page - 42

    Chapter 2 [ 31 ] You'll see the preceding coding pattern in the majority of the services we cover in the book. However, the code examples for the various chapters do not include the IIFE since the Grunt script for each project wraps all of the code in an IIFE as part of the build process. Configuring your service Earlier in this chapter, we discussed using the read more..

  • Page - 43

    Designing Services [ 32 ] Summary In this chapter, we discussed several best practices you can use when designing your AngularJS services, along with several best practices that you can follow to ensure your services are written with testability in mind. We then covered the various ways you can define your service. We discussed using the constant and value methods to read more..

  • Page - 44

    Testing Services Whether you are a test-first or a test-last coder, it doesn't really matter. A unit test suite should be mandatory for every unit of code you write. Why? Because it not only shows that your code meets specifications, but it also helps provide documentation for how your code works and a guide to ensure that your code continues to work as you add new read more..

  • Page - 45

    Testing Services [ 34 ] A scenario file begins with a call to the global Jasmine function, describe , with two parameters: a string and a function. The string is a name or title for the scenario, usually what is under test. The function is a block of code that implements the scenario: describe('authenticate service', function(){ … specifications go read more..

  • Page - 46

    Chapter 3 [ 35 ] If there are steps you need to execute prior to running the tests in a test suite, you can use the beforeEach method to execute them before each test declared inside an it function is executed. If you need to execute steps to clean up after each test, you can use the afterEach method to execute them after each test is executed. To read more..

  • Page - 47

    Testing Services [ 36 ] Once your module has been loaded into the test scenario, you'll need to get an instance of your service so that you can test the various methods of your service. To do this, you can use the inject method provided by the AngularJS Mocks library. The inject method allows you to define the dependencies you want to instantiate for use by read more..

  • Page - 48

    Chapter 3 [ 37 ] Sometimes, when you create mock data for your tests, you will unknowingly create test data that will always meet the conditions of the code under test. To ensure that your code handles all the various types of data, it helps to use a library that will randomly generate the data for you. If your code is written properly, it should handle any data read more..

  • Page - 49

    Testing Services [ 38 ] The following is a screenshot of using the Chrome extension, Postman, to capture data from the brewerydb.com API: The last thing to cover when talking about mocking data is how to use data from a database to mock your data. This becomes really easy if you execute your tests using a test runner such as Karma using Node.js. As you are already read more..

  • Page - 50

    Chapter 3 [ 39 ] One of the things that makes mocking services easy is AngularJS' dependency injection functionality. If we need to mock out services in our application, we can define a mock service in our unit test; we can register this service with AngularJS to replace the other service. The AngularJS module loader uses a last-in-wins methodology when it comes to read more..

  • Page - 51

    Testing Services [ 40 ] }); beforeEach(module('application.services')); beforeEach(module('mock.log')); beforeEach(inject(function (authenticate, log_service) { service = authenticate; })); // ... // test code // ... }); If you have a lot of services that need mocking, you can move the module definitions to a separate JavaScript file, but just make read more..

  • Page - 52

    Chapter 3 [ 41 ] service.logOut(); expect(log.trace).toHaveBeenCalled(); }); If you wanted to evaluate the parameters passed in to a method, you can use the toHaveBeenCalledWith expectation method as shown in the following code: it("should call log trace with the method name", function(){ spyOn(log, "trace"); service.logOut(); read more..

  • Page - 53

    Testing Services [ 42 ] Handling dependencies that return promises If you've spent some time writing services in AngularJS that interact with Web APIs, you have probably used the $http service. One of the prevalent code patterns that tends to develop involves calling the then method on the promise that is returned from the various $http methods. This, in effect, waits read more..

  • Page - 54

    Chapter 3 [ 43 ] data = { retrieveUser: function (email) { deferred = q.defer(); return deferred.promise; } }; return data; }); beforeEach(module('application.services')); beforeEach(module('test.data')); beforeEach(inject(function (authenticate data_service, read more..

  • Page - 55

    Testing Services [ 44 ] We'll also need to hold on to the deferred object created by the call to q.defer() so that we can resolve the promise, which in turn will execute the then() clause in our service's code. Next, we define our mock service in a beforeEach() call so that each time a test is executed, we have a clean version of our mock service. Our mock read more..

  • Page - 56

    Chapter 3 [ 45 ] Mocking backend communications One of the toughest things to do when testing a service is to mock the calls to external services. However, you're lucky; the AngularJS Mocks library provides a mock service that makes this a breeze. The $httpBackend service provides an HTTP backend implementation that can be used in your service unit tests. The service can read more..

  • Page - 57

    Testing Services [ 46 ] The following unit test provides an example of how to use the $httpBackend mock service to set up a default handler using the whenGet method to handle any calls to url '/users' and respond with a list of users. The unit test sets up an expectation with a call to the expectGet method that expects a call to url '/users/1' and read more..

  • Page - 58

    Chapter 3 [ 47 ] Using the $httpBackend mock service, your unit tests can be written to ensure that your service code handles the various combinations of data returned by the external services it interacts with. You have total control of the status and data returned by the call, thus allowing you to exercise all the various paths in your code. Mocking timers One last read more..

  • Page - 59

    Testing Services [ 48 ] }); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); }); The preceding unit test provides an example of how to use the $interval mock service to skip 30 seconds ahead during our unit test, thus causing the service under test to make an HTTP request to url '/status' . read more..

  • Page - 60

    Handling Cross-cutting Concerns A good application design uses layers to separate areas of responsibility. If done right, each layer has a single responsibility and it interconnects with the other layers using a well-defined interface. The most popular layers you'll see included in an application are data, business logic, and the user interface. However, there are services that cut read more..

  • Page - 61

    Handling Cross-cutting Concerns [ 50 ] Although callback methods and promises work really well for notifying a single consumer once your service method has completed, they fall way short when you have multiple consumers that need to know when your service's methods complete or when data managed by your service is updated. This especially occurs when you have multiple views read more..

  • Page - 62

    Chapter 4 [ 51 ] It is better to use a simple publish /subscribe messaging service that leverages callbacks. This reduces the strain on the digest loop of rootScope and keeps the number of items that need to be evaluated during the digest loop to a minimum. One thing to keep in mind when leveraging callbacks for messaging is that you can slow down the read more..

  • Page - 63

    Handling Cross-cutting Concerns [ 52 ] var service = { publish: publish, subscribe: subscribe, unsubscribe: unsubscribe }; return service; }); You can find the code and unit tests in the samples for this book under the service folder. This implementation of the publish/subscribe pattern is rather read more..

  • Page - 64

    Chapter 4 [ 53 ] To use the service, a consumer needs to include the messaging service as a dependency and then subscribe to one or more topics that it's interested in being notified about when the event occurs. The following is a sample controller that uses the messaging service to authenticate a user: angular.module('brew-everywhere').controller('LoginCtrl', function($scope, read more..

  • Page - 65

    Handling Cross-cutting Concerns [ 54 ] The controller then defines a message handler called authenticateUserCompletedHandler that is registered with the messaging service with the topic _AUTHENTICATE_USER_COMPLETE_ . It then defines the onLogin method that is used as an ng-click handler to log the user in by publishing the message _AUTHENTICATE_USER_ with username and read more..

  • Page - 66

    Chapter 4 [ 55 ] }; var endRequestHandler = function() { // got the request start notification, show the element element.hide(); }; scope.startHandle = messaging.subscribe( events.message._SERVER_REQUEST_STARTED_, startRequestHandler); read more..

  • Page - 67

    Handling Cross-cutting Concerns [ 56 ] var displayDialogHandler = function(message, type){ messageText = message; displayType = type; $timeout(function(){ switch(displayType){ case 'popup': messaging.publish(events.message._DISPLAY_POPUP_, [messageText]); break; read more..

  • Page - 68

    Chapter 4 [ 57 ] The dialog service again has the messaging and events services as dependencies along with the $timeout service. The service consists of one message handler displayDialogHandler , which takes the message and type of dialog to display, and translates that into one of these two messages, _DISPLAY_POPUP_ and _DISPLAY_ CONFIRMATION_ . The $timeout service is read more..

  • Page - 69

    Handling Cross-cutting Concerns [ 58 ] scope.modalType = 'popup'; element.show(); }; var showConfirmationHandler = function(messageText) { // got the request start notification, show the element scope.message = messageText; scope.modalType = 'confirmation'; read more..

  • Page - 70

    Chapter 4 [ 59 ] The last user notification service we are going to cover is a simple notification list directive that works with two services to display errors and alerts to the user. The error and notification services are similar, except each one handles a different message. This split is on purpose because of the single responsibility principle and it allows you to read more..

  • Page - 71

    Handling Cross-cutting Concerns [ 60 ] }; return errors; }) .run(function(errors){ errors.init(); }); You can review the notification service's code in the code samples for the book. The service has an internal array that is used to hold the error messages as they are received by the service. The addErrorMessageHandler message handler uses the $timeout service read more..

  • Page - 72

    Chapter 4 [ 61 ] messaging.publish(events.message._CLEAR_ERROR_MESSAGES_); }; messaging.subscribe(events.message._ERROR_MESSAGES_UPDATED_, scope.onErrorMessagesUpdatedHandler); scope.onUserMessagesUpdatedHandler = function (userMessages) { if(!scope.notifications){ scope.notifications = read more..

  • Page - 73

    Handling Cross-cutting Concerns [ 62 ] var init = function (logName) { log = log4javascript.getLogger(logName); }; var setLogLevel = function (level) { log.setLevel(level); }; var setLogAppender = function (appender) { log.addAppender(appender); }; var trace = function (message) { log.trace(message); }; read more..

  • Page - 74

    Chapter 4 [ 63 ] }; messaging.subscribe(events.message._LOG_FATAL_, fatal); var service = { init: init, setLogLevel: setLogLevel, setLogAppender: setLogAppender }; return service; }) .run(['logging', function (logging) { logging.init('main'); logging.setLogLevel(log4javascript.Level.ALL); logging.setLogAppender(new log4javascript. read more..

  • Page - 75

    Handling Cross-cutting Concerns [ 64 ] $scope.traceDebug = function(message){ messaging.publish(events.message._LOG_DEBUG_, [message]); }; $scope.traceInfo = function(message){ messaging.publish(events.message._LOG_INFO_, [message]); }; $scope.traceWarning = function(message){ messaging.publish(events.message._LOG_WARNING_, [message]); }; $scope.traceError = read more..

  • Page - 76

    Chapter 4 [ 65 ] With this new base controller, we can change the login controller and remove some of the code clutter in the controller. You can also see where we are calling the traceInfo method on the scope to log events as our program executes: angular.module('brew-anywhere').controller('LoginCtrl', function($scope, $controller, events){ // this call to read more..

  • Page - 77

    Handling Cross-cutting Concerns [ 66 ] Authentication using OAuth 2.0 The final cross-cutting service layer we are going to talk about is the authentication layer. The authentication layer is responsible for ensuring that the people interacting with our application are the people they say they are. There are several ways that someone can authenticate himself or herself with our read more..

  • Page - 78

    Chapter 4 [ 67 ] ['Log In Failed.', 'alert-warning']); }; messaging.subscribe(events.message._GPLUS_FAILED_, authenticationFailureHandler); var login = function(username, password){ currentUser = {name: {givenName: "Test", surname: "User"}}; messaging.publish( read more..

  • Page - 79

    Handling Cross-cutting Concerns [ 68 ] The googlePlusAuthentication service handles the _GPLUS_AUTHENTICATE_ message, which kicks the Google+ authentication process off with a call to the gapi. auth.authorize method passing in the client ID of your Google+ application. Once the library returns, the callback handler parses the response and then proceeds to request the user's read more..

  • Page - 80

    Chapter 4 [ 69 ] var requestUserInfo = function () { var url = buildUserProfileUrl(); $http.get(url).then(userProfileSuccessHandler, userProfileErrorHandler); }; var onAuthenticateHandler = function() { login(); }; messaging.subscribe(events.message._GPLUS_AUTHENTICATE_, onAuthenticateHandler); You can review the complete source in the read more..

  • Page - 81

    Handling Cross-cutting Concerns [ 70 ] We also saw that by using the publish/subscribe messaging pattern, our services were so loosely coupled that we had to provide the init methods to ensure AngularJS would load them into our application. We also discussed that because of that loose coupling, we are able to replace a service without major code changes. This is read more..

  • Page - 82

    Data Management Every application is dependent on data; no matter what type of application you write, be it a weather app, a stock quote app, or a line-of-business app, they all use data in one form or another. Managing the data in your application is one of the most basic things you'll have to do. If you are pulling data directly from a REST-based service or read more..

  • Page - 83

    Data Management [ 72 ] K. Scott Allen, author of Professional ASP.NET MVC 4, Wrox, and numerous pluralsight courses, wrote a great article, Building Better Models for AngularJS at http://odetocode.com , which describes a good way to do this using AngularJS values to define both your model and the associated code to encapsulate everything into an easy-to-use service that your read more..

  • Page - 84

    Chapter 5 [ 73 ] * calculates the gravity per pound for the fermentable * @param brewHouseEfficiency - the estimated brew house * efficiency * @returns {number} - potential extract per pound for the * fermentable */ gravityPerPound: function(brewHouseEfficiency){ return ((this.potential - 1) * 1000) * brewHouseEfficiency; }, read more..

  • Page - 85

    Data Management [ 74 ] K. Scott Allen also provides a generic translation service that allows you to transform the plain JSON objects received from external data services into your model object. The modelTransformer services takes the JSON object and a model value and then uses the AngularJS extend method to apply the model to the JSON object: angular.module('brew-everywhere') read more..

  • Page - 86

    Chapter 5 [ 75 ] Implementing a CRUD data service Our application stores its data in a mongodb database hosted by https://mongolab. com . There are 10 different collections that we have to interact with, and these include adjuncts, brewers, equipment, fermentables, hops, mashprofiles, recipes, styles, waterprofiles, and yeast. We need to create a data service for each one of read more..

  • Page - 87

    Data Management [ 76 ] var setBaseUrl = function (uri) { baseUrl = uri; }; var getBaseUrl = function () { return baseUrl; }; var query = function (database, collection, parameters) { parameters = parameters || {}; parameters['apiKey'] = apiKey; var uri = baseUrl + '/' + database + '/collections/' + read more..

  • Page - 88

    Chapter 5 [ 77 ] setApiKey: setApiKey, getApiKey: getApiKey, setBaseUrl: setBaseUrl, getBaseUrl: getBaseUrl, query: query, queryById: queryById, create: createObject, update: updateObject, delete: deleteObject }; return mongolab; }); The mongolab service is pretty straightforward; each CRUD method essentially read more..

  • Page - 89

    Data Management [ 78 ] Each service consists of a core set of methods: get{type} , get{type}ById , create{type} , update{type} , and delete{type} , where {type} is one of the collection object types mentioned earlier. The service also uses the messaging service to react to events to execute the associated service calls and respond once the mongolab service call read more..

  • Page - 90

    Chapter 5 [ 79 ] var getAdjunctErrorHandler = function(){ messaging.publish(events.message._GET_ADJUNCTS_FAILED_); messaging.publish(events.message._ADD_ERROR_MESSAGE_, ['Unable to get adjuncts from server', 'alert.error']); }; messaging.subscribe(events.message._GET_ADJUNCTS_, getAdjuncts); … … var init = function(){ adjuncts = []; }; var read more..

  • Page - 91

    Data Management [ 80 ] You can provide some simple caching to your data services using the $cacheFactory service. What you'll need to do in your service is get a reference to the cache instance and then check it prior to making a call to the mongolab service. When you create a new object in the collection, you can insert the returned item into the collection read more..

  • Page - 92

    Chapter 5 [ 81 ] Now, the $cacheFactory service works great for caching data in your AngularJS application while the application is running, but it does nothing for caching data between instances of your AngularJS application. To cache data across instances of your AngularJS application, you need to look at taking advantage of the browser's local storage mechanism. Luckily, read more..

  • Page - 93

    Data Management [ 82 ] Notice how the getAdjuncts method now pulls the adjunct list from the localStorageService , and if the list exists, it returns the list without calling the mongolab service. The getAdjunctSuccessHandler has also been updated to store the resulting adjunct list in local storage using the set method of localStorageService . The previous solutions to read more..

  • Page - 94

    Chapter 5 [ 83 ] The following fermentableType filter expects an array of fermentable objects as the input parameter and a type value to filter as the arg parameter. If the fermentable's type value matches the arg parameter passed into the filter, it is pushed onto the resultant array, which will in turn cause the object to be included in the set provided to read more..

  • Page - 95

    Data Management [ 84 ] The result of calling fermentableType with the value, Grain is only going to display those fermentable objects that have a type property with a value of Grain . Using filters to reduce an array of objects can be as simple or complex as you like. The next filter we are going to look at is one that uses an object to reduce the read more..

  • Page - 96

    Chapter 5 [ 85 ] <th>SRM</th> <th>Amount</th> <th>&nbsp;</th> </tr> </thead> <tbody> <tr ng-repeat="fermentable in fermentables | filterFermentable:{type:'Grain', color: 3}"> <td read more..

  • Page - 97

    Data Management [ 86 ] Another thing you can do is translate an array of objects into a different type or reduced object. This works the same way as the two preceding filters, except that you create a new object and push it onto the resultant array. The following is an example of a filter that translates the array provided in the input parameter and returns a read more..

  • Page - 98

    Chapter 5 [ 87 ] Using AngularJS filters to reduce and translate your application's data is another best practice you can leverage to encapsulate the business logic associated with your application's data. You will find that a well-planned filter will address the majority of your needs when it comes to reducing and translating your data. If you find that an AngularJS read more..

  • Page - 99

    read more..

  • Page - 100

    Mashing in External Services As the Internet has grown, more and more innovative web-based applications have appeared that leverage the public APIs of cloud-based services. Nowadays, travel sites provide flight listings from airline booking systems, hotel listings from various hotel reservation systems, car rentals from rental companies, and tickets to popular tourist attractions from read more..

  • Page - 101

    Mashing in External Services [ 90 ] We can also take advantage of various features such as notifications to alert the user when to perform an action on a batch of beer without having to build notification services and native applications; instead, we can configure events to use the existing Google Calendar features built into mobile devices and other web services to handle read more..

  • Page - 102

    Chapter 6 [ 91 ] messaging.subscribe(events.message._GET_CALENDAR_, getCalendarByName); var handleCalendarClientLoaded = function() { messaging.publish(events.message._SERVER_REQUEST_STARTED_); var request = gapi.client.calendar.calendarList.list(); request.execute(handleGetCalendarList); }; var handleGetCalendarList = function(resp) { if(resp.items.length > 0){ angular.forEach(resp.items, function(item){ read more..

  • Page - 103

    Mashing in External Services [ 92 ] getCalendarFailed(); } }; var getCalendarFailed = function(){ messaging.publish(events.message._SERVER_REQUEST_ENDED_); messaging.publish(events.message._GET_CALENDAR_FAILED_); }; To initialize the Google Calendar API library, we use the gapi.client.load method of the Google JavaScript client library to load the library asynchronously. Once the read more..

  • Page - 104

    Chapter 6 [ 93 ] The rest of the service's methods use the basic CRUD pattern to create, update, and delete events for a given calendar. Each has an associated callback handler, which calls the method getGoogleCalendarById to reload the calendar after it has been updated. We use this pattern to take advantage of the messaging service by refreshing the calendar and then read more..

  • Page - 105

    Mashing in External Services [ 94 ] var event = {'summary': 'Brew: ' + currentRecipe.Name, 'description': 'Brew: ' + currentRecipe.Name, 'start': {'date': formattedBrewDate}, 'end': {'date': formattedBrewDate}, 'reminders': {'useDefault': true}}; messaging.publish(events.message._CREATE_EVENT_, [brewingCalendarId, event]); }; First, we have to format the read more..

  • Page - 106

    Chapter 6 [ 95 ] 'https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/tasks'] }); }) With this change, we now have read-write access to the user's Google Tasks, allowing us to add new brewing tasks to the user's existing task list or one we create specifically for our application. Our service will start out with a method that read more..

  • Page - 107

    Mashing in External Services [ 96 ] else { getTaskListFailed(); } } }; var getTaskListFailed = function(){ messaging.publish(events.message._SERVER_REQUEST_ENDED_); messaging.publish(events.message._GET_TASK_LIST_FAILED_); }; var getGoogleTaskListById = function(id){ var request = gapi.client.tasks.tasks.list({'tasklist': id}); request.execute(handleGetGoogleTaskListById); }; read more..

  • Page - 108

    Chapter 6 [ 97 ] Again, we start out with the gapi.client.load method requesting the tasks API and pass in the callback handler, handleTasksClientLoaded , which will be called once the JavaScript library is loaded. Once the library is loaded, we make a call to the tasks.tasklist.list method to retrieve a list of the user's task lists and pass in the callback handler. read more..

  • Page - 109

    Mashing in External Services [ 98 ] Again based on the method called, a corresponding call is made to the tasks. tasks.x method to insert, update, and delete a task. A callback method is provided to handle the response from the API call and if successful, a call is made to getGoogleTaskListById , which will in turn retrieve the updated list and publish the read more..

  • Page - 110

    Chapter 6 [ 99 ] the user's calendar or task list can do it via this new service in a more refined way. The brewCalendar service wraps the business logic needed to manage both the brewing calendar and brewing task list. It also handles the logic to create all of the calendar events and tasks for a brew day: angular.module('brew-everywhere').factory('brewCalendar', function read more..

  • Page - 111

    Mashing in External Services [ 100 ] getBrewingTaskList(); } else { createDefaultCalendarEntries(brewingCalendarId); } }; messaging.subscribe(events.message._GET_CALENDAR_COMPLETE_, onHandleGetCalendarComplete); var getBrewingTaskList = function(){ messaging.publish(events.message._GET_TASK_LIST_, [brewingTaksListName, true]) }; messaging.subscribe(events.message._GET_BREWING_TASKS_, read more..

  • Page - 112

    Chapter 6 [ 101 ] You can view the full source code in the sample files for the book. Whenever a consumer publishes the _GET_BREWING_CALENDAR_ event, the service makes a call to the googleCalendar service to get the calendar with the name "My Brewing Calendar" and true for the create parameter. When the googleCalendar service returns the calendar, the read more..

  • Page - 113

    Mashing in External Services [ 102 ] Creating these events and tasks can be quite extensive; the start and end dates need to be calculated for each event and the ingredient list needs to be compiled. The service breaks each of these pieces of business logic into one of several methods and a single method calls each of these sub-methods to make sure all of the read more..

  • Page - 114

    Chapter 6 [ 103 ] var dueDate = moment(scheduledDate).subtract('days', 2); var ingredientList = 'Gather the following ingredients:\n\n'; angular.forEach(currentRecipe.Fermentables, function(item){ ingredientList += item.Amount + ' - ' + item.Name + ' ' + item.Type + '\n'; }); angular.forEach(currentRecipe.Adjuncts, function(item){ ingredientList += read more..

  • Page - 115

    Mashing in External Services [ 104 ] The addPrimaryToCalendar method, given in the following code, shows some of the date calculations that go into adding a calendar event that spans a given period of time. First, the method checks the recipe to make sure that the brewer has added a primary fermentation step. Then, it starts calculating the end date by adding the read more..

  • Page - 116

    Chapter 6 [ 105 ] Summary Having the ability to integrate cloud-based services into our application allows us to add additional functionality to our AngularJS applications without having to code all of the core logic required to implement such functionality. In this chapter, we covered how to integrate Google's Calendar and Tasks APIs into our application by creating two generic read more..

  • Page - 117

    read more..

  • Page - 118

    Implementing the Business Logic Over the past several years, the knowledge base around JavaScript has matured tremendously. Web applications appeared on the scene with more and more functionality as JavaScript libraries helped to accelerate the move from server-based applications to large client-based applications. This trend of moving functionality from the server to client was due to read more..

  • Page - 119

    Implementing the Business Logic [ 108 ] Encapsulating business logic in models In Chapter 2, Designing Services, and Chapter 5, Data Management, we looked at how to create model services in our application using the value provider and a standard JavaScript constructor definition. This allowed us to inject the model into our controllers, directives, and services the same way read more..

  • Page - 120

    Chapter 7 [ 109 ] * @param brewHouseEfficiency - the estimated brew house * efficiency * @returns {number} - potential extract per pound for the * fermentable */ gravityPerPound: function(batchType, brewHouseEfficiency){ var result = ((this.Potential - 1) * 1000.0); switch(batchType) { case "All Grain": read more..

  • Page - 121

    Implementing the Business Logic [ 110 ] return (this.ingredientGravity(batchType, brewHouseEfficiency) / batchVolume); }, /** * calculates the color the fermentable will contribute to a * batch of beer based on the amount of the feremntable * fermentable used in the recipe * @returns {number} - the srm color read more..

  • Page - 122

    Chapter 7 [ 111 ] Encapsulating business logic in services Another common way to incorporate your business logic is to create a service that exposes various functions which encapsulate the business logic and take arrays of objects as parameters to operate as the business logic requires. We could just as easily take the same code and wrap it into a service that provides read more..

  • Page - 123

    Implementing the Business Logic [ 112 ] * @returns {number} - returns the total potential extract for * the fermentable */ var ingredientGravity = function(fermentable, batchType, brewHouseEfficiency){ return this.Amount * (gravityPerPound(fermentable, batchType, brewHouseEfficiency) / 1000); }, /** * read more..

  • Page - 124

    Chapter 7 [ 113 ] * @returns {number} - the srm color value for the fermentable */ var colorPoints = function(fermentable, batchVolume){ return this.srm(fermentable) / batchVolume; } var helper = { gravityPerPound: gravityPerPound, ingredientGravity: ingredientGravity, gravityPoints: gravityPoints, srm: srm, colorPoints: read more..

  • Page - 125

    Implementing the Business Logic [ 114 ] I usually add small calculations that represent calculated values to the model object. This helps reduce the number of services I need to inject into a controller and is handy when I need to display the calculated values in a view. I create services to encapsulate large amounts of complex business logic in my application. This read more..

  • Page - 126

    Chapter 7 [ 115 ] As the brewer brews a batch of beer, there are several phases. The process starts with the preheating of water, the mashing in of grains, and the steeping of the grains to convert the starch into sugar. It ends with the boiling of the sugar and water mixture, which is called wort, to reach the desired ratio of sugar in the wort, prior to read more..

  • Page - 127

    Implementing the Business Logic [ 116 ] The following is the definition for part of our process; you can review the complete version of the file in the source of this book, in the model directory in the file called brewingProcessStateMachine.js : { id: 'BrewProcess', processController: 'BrewProcessController', title: 'Brew Process', description: 'UI Process flow for read more..

  • Page - 128

    Chapter 7 [ 117 ] mash temperature', commands: [ { id: 'Waiting', transition: 'PreHeatMashHeating'}, { id: 'TemperatureReached', transition: 'MashTempReached'}, { id: 'Exit', transition: 'exit'}, { id: 'DisplayErrorMessage', transition: read more..

  • Page - 129

    Implementing the Business Logic [ 118 ] The naming convention for the functions that will be executed when a command is triggered is similar. The command needs to be named <state.id><command.id> , where state.id is the ID of the state and command.id is the ID of the command. So, if we wanted to provide a function for the Waiting command in the Boil read more..

  • Page - 130

    Chapter 7 [ 119 ] As I mentioned earlier, the state machine is designed to be used in conjunction with other services such as the view controller service; it doesn't do much more than maintain the current state and find the state associated with a transition or command. To navigate to the different pages in our application, we need to switch to the URL defined in read more..

  • Page - 131

    Implementing the Business Logic [ 120 ] } // check to make sure we have a currentState // and return success if we do if (controller.currentState) { // execute and pre processing for the new state controller read more..

  • Page - 132

    Chapter 7 [ 121 ] The business rules engine we are going to look at uses a forward-chaining algorithm to walk through a set of rules and execute them by evaluating the facts passed into the execute method of the service. For those not familiar with rules engines, facts are the data we need to pass into the rules engine; the rules expect to evaluate these facts to read more..

  • Page - 133

    Implementing the Business Logic [ 122 ] fact.passOneMatches.push(style); } }); cb(false); }, "consequence": function (cb) { cb(); } }, … source removed for brevity … { "name": "match srm style parameters", read more..

  • Page - 134

    Chapter 7 [ 123 ] To call the rules engine from our controller, we just need to inject the rulesEngine service and the rules constants into our controller, and call it as follows: rulesEngine.init(rules.styleRules); var fact = { recipe: $scope.currentRecipe, styles: $scope.currentStyles }; rulesEngine.execute(fact, function(result){ $scope.matchingStyles = result.result; read more..

  • Page - 135

    read more..

  • Page - 136

    Putting It All Together To tie everything together, we are going to look at a sample web application built upon the services we've covered throughout the book. The sample web application is a tool for home brewers to help with the formulating and brewing of beer recipes. Brew Everywhere is an AngularJS application that provides home brewers with the tools to enjoy their read more..

  • Page - 137

    Putting It All Together [ 126 ] Wiring in authentication First, we'll start with how the application handles authenticating a brewer. Back in Chapter 4, Handling Cross-cutting Concerns, we discussed the authenticate service that allows us to authenticate the user against either a data store or external authentication service such as Google+ or Facebook. The authenticate service read more..

  • Page - 138

    Chapter 8 [ 127 ] The second mechanism to display notifications to the user is the dialog directive. This directive uses the messaging service to listen for two events: _DISPLAY_POPUP_ and _DISPLAY_CONFIRMATION_ . Each message causes the dialog directive to display an HTML-based modal dialog to the user with the message text that was sent with the event. When the user read more..

  • Page - 139

    Putting It All Together [ 128 ] As the user moves through the brewing process, the various controllers for each view that is displayed to the user will invoke the getTargetStateForCommand method on the viewController service to move to the state associated with the command. The viewController service also invokes the pre, post, and command functions associated with each read more..

  • Page - 140

    Chapter 8 [ 129 ] The mongolab service is the only service in the application that does not use the messaging service to communicate with its consumers. This is because each call to the mongolab service needs to be handled directly by the consumer that invoked it. If we had used the messaging service, we could not have made the service generic enough to query read more..

  • Page - 141

    Putting It All Together [ 130 ] Also, when the googleTask service finishes retrieving the brewing tasklist, it publishes the _GET_TASK_LIST_COMPLETE_ event, which causes the brewingCalendar service to process the returned data and publish the _GET_ BREWING_CALENDAR_COMPLETE_ event that allows the home controller to update its internal array of brewing tasks, in turn updating the read more..

  • Page - 142

    Chapter 8 [ 131 ] stores them off to internal arrays that it then uses to display tables of the various ingredients as the user navigates the various tabs of the recipe view. When the user adds an ingredient to the recipe, the recipe model iterates through each of the recipe's arrays of ingredient models to calculate the following beer style parameters; original gravity, read more..

  • Page - 143

    Putting It All Together [ 132 ] Messaging is the heart of the application One of the services that we've touched on throughout this chapter but never really mentioned is the messaging service. It is the workhorse of this application and is responsible for delivering events between the various services, controllers, and directives used in the application. All of the read more..

  • Page - 144

    Chapter 8 [ 133 ] We used directives to encapsulate code that manipulates the DOM and validate data. This again follows best practices by limiting DOM manipulation to the correct component and helps to keep our controller as thin as possible by moving validation code out of the controller to directives where it works best. Next, we used value providers to define the read more..

  • Page - 145

    read more..

  • Page - 146

    Index Symbols $cacheFactory service 79-81 $inject service 22 A addGetIngredientsTask method 103 addPrimaryToCalendar method 104 afterEach method 35 AngularJS best practices 8 components 7 application analytics logging 61-65 application flow controlling 127, 128 assignments constructors, limiting to 20 authenticate service 126 authentication performing, OAuth 2.0 used 66-69 wiring in read more..

  • Page - 147

    [ 136 ] directives about 9 functionalities 9, 10 Document Object Model (DOM) 8 E errors displaying 126, 127 logging 61-65 events storing, with Google Calendar 89-94 external services data, displaying from 128-130 F Facebook 126 factories overview 22-28 factory method 26 Fermentable model 72 fermentableType filter 83 G getAdjuncts method 80-82 getBrewingTaskList method read more..

  • Page - 148

    [ 137 ] O OAuth 66 OAuth 2.0 used, for authentication 66-69 OpenID 66 P patterns used, for communicating with service's consumers 49-54 promises using, sparingly 21 provider method 24, 27, 31 providers overview 22-28 publish method 52 publish/subscribe pattern about 50 advantages 132 R recipe building 130, 131 calculating 130, 131 retrieveUser method 42 Revealing read more..

  • Page - 149

    read more..

  • Page - 150

    Thank you for buying AngularJS Services About Packt Publishing Packt, pronounced 'packed', published its first book "Mastering phpMyAdmin for Effective MySQL Management" in April 2004 and subsequently continued to specialize in publishing highly focused books on specific technologies and solutions. Our books and publications share the experiences of your fellow IT professionals in read more..

  • Page - 151

    AngularJS Directives ISBN: 978-1-78328-033-9 Paperback: 110 pages Learn how to craft dynamic directives to fuel your single-page web applications using AngularJS 1. Learn how to build an AngularJS directive. 2. Create extendable modules for plug-and-play usability. 3. Build apps that react in real time to changes in your data model. Mastering Web read more..

  • Page - 152

    Dependency Injection with AngularJS ISBN: 978-1-78216-656-6 Paperback: 78 pages Design, control, and manage your dependencies with AngularJS dependency injection 1. Understand the concept of dependency injection. 2. Isolate units of code during testing JavaScript using Jasmine. 3. Create reusable components in AngularJS. Instant AngularJS Starter ISBN: read more..

  • Page - 153

    read more..

Write Your Review