Guest post David is co-founder of Aerobatic, a cloud hosting platform optimized for single page HTML5 applications.
More than ever it is possible to build sophisticated web applications using nothing more than the technologies available in the browser, namely Javascript, HTML, and CSS. In this post I will walk through the process of building and testing a single page contact manager application with AngularJS and the Salesforce REST API.
The sample app is hosted on the Aerobatic cloud platform which is highly specialized for just this sort of HTML5 / REST API application. While nearly all of the code will work without modification as static files on their own, Aerobatic provides an enhanced platform that further streamlines the delivery of single page apps including: a smart API proxy, simulator mode that eliminates test environments, traffic routing, automatic CDN hosting, and more.
This post builds upon a recent post I wrote over on the Aerobatic blog entitled: Securing an HTML5 Salesforce Connected App. That article describes some different approaches to handling the two basic aspects of securing a Salesforce connected app: authenticating via the Salesforce OAuth service, and utilizing the access token to make authenticated API calls. I suggest you read that article first, then come back here to learn how the rest of the sample application is built and tested.
Live Demo and Source Code
You can try the sample app out for yourself at https://sfcontacts.aerobaticapp.com. All the source code is available on GitHub at https://github.com/aerobatic/sfcontacts. Check out the README for instructions on how to fork the app to play around with and extend on your own.
The Case for Single Page Applications
Before diving into the technical details, it’s worth talking briefly about why the single page approach is emerging as the preferred way to build modern web applications. In my mind it really boils down to two things. Most importantly is the enhanced level of responsiveness and smoothness to the user interactions. A well implemented single page app feels more akin to a native mobile application than the traditional collection of hyperlinked web pages.
Secondly SPAs force developer focus. Every web application requires client Javascript, HTML, and CSS be written, it’s unavoidable. So rather than split developer attention between a server framework like Rails, Node, PHP, etc. and the front-end, why not build the entire application using just the front-end tools and languages that are needed anyhow? This naturally leads to a more service oriented architecture since the web app is forced to rely on remote services for working with live data – either ready made ones like the Salesforce REST API or your company’s own REST services.
The biggest knock against SPAs has been that they were hard to build, led to fragile code, and difficult to test. Fortunately robust Javascript frameworks like Angular and Ember have gone a long way to quell those criticisms and are increasingly up to the task of running even the most complex of application front-ends.
Alright, enough with the background, let’s get to the meat.
Application Structure
Our application is structured using common Angular conventions. Every individual module including services, controllers, and directives are housed in a dedicated .js file. The application uses Grunt for running the tasks that power the developer workflow (including the grunt-aerobatic tasks) and bower for client dependency management. Our Gruntfile is configured to combine and minify all our scripts into one so there is no penalty for structuring the code for optimal maintainability.
Third Party Dependencies
- Angular – Goes without saying.
- Lodash – Lots of nice code-saving utility functions.
- Bootstrap – Let’s us non-designers build reasonably attractive UIs. Also provides a solid foundation for responsive layouts that adjust according to the screen size.
- Bootswatch – A nice collection of custom Bootstrap themes. I’m using the Flatly theme.
- AngularUI Bootstrap – Native AngularJS implementation of the Bootstrap Javascript components.
- Bower – Install client library dependencies.
- Grunt – Powers the developer build and deploy workflow.
Gruntfile Setup
The Gruntfile is configured to handle common build tasks like combining and minifying scripts and stylesheets. It’s also using the ngAnnotate task which converts shorthand Angular dependency injection declarations to the long form that can be safely minified. This allows authoring simpler, more readable Angular code.
The grunt-aerobatic module includes the sim and deploy tasks. Running grunt sim
will start the development simulator for running the app locally. It’s actually a hybrid mode where the single page is running in the cloud but assets are downloaded from localhost. Running grunt deploy
deploys a new version of the app to the cloud.
The development simulator works in conjunction with the grunt watch task to enable livereload behavior where the browser reloads itself whenever a file changes.
Salesforce Service
Rather than calling the Salesforce REST API directly from the controller, we can achieve better separation of concerns by encapsulating all API interactions within an Angular service module. The Salesforce service exposes three basic CRUD operations: loadContacts
, createContact
, and updateContact
(deleteContact
is left as an exercise for the reader). The $http service, like all things asynchronous in Angular, is built on promises provided by the $q service. It pays to get familiarized with how Angular handles promises. I found them a little tricky to initially get the hang of, but they are a powerful way to manage complex asynchronous code flows without descending into callback hell.
The relevant Salesforce for the API calls can be found here:
The buildHttpConfig
is a helper function that builds up the config object that is passed to the $http
service. The Salesforce REST API is not CORS enabled, so you can’t call it directly from the browser on a different domain. Aerobatic provides an API proxy that will relay the call on behalf of the browser. If you are using the Salesforce user-agent OAuth flow, the access token would be passed in the X-Authorization header. However Aerobatic provides a built-in OAuth option that stores the access token on the server in session state. Rather than pass the token to the browser, a token placeholder @@user.accessToken@@
is specified which the proxy will substitute with the actual token that was granted by Salesforce – eliminating the need for the browser to store sensitive information. The security blog post covers this in more detail.
The loadContacts
function runs an SOQL query to fetch a subset of attributes for each of the contacts. I could have returned the result of the $http service directly but I prefer wrapping it in a deferred so that the Salesforce service has an opportunity to act on the response data or do specialized error handling before passing control back to the caller.
The createContact
method makes a POST request passing the contact object in the body. Angular will automatically take care of serializing the JSON object into a string. I’m taking advantage of the fact that Angular supports POJO models (plain old Javascript objects), so the app can seamlessly pass contacts into and out of the API without any extra mapping. A case could be made that this creates an unwanted coupling, but for these simple objects it works fine and it simplifies the code. The JSON response returns the id of the new contact entity which I assign to the Id attribute of the original contact. I’m not sure why the id is lowercase in the API response but uppercase in the contact schema.
The updateContact
function makes a PATCH request where the Id of the contact to be updated is specified in the path. I’m omitting the Id attribute from the contact in the body of the request as only the attributes with modified values should be specified, and the Id never changes. All remaining attributes are being passed, but if the app knew that only certain attributes like say the FirstName and LastName were modified, just those two could be passed saving some network bandwidth.
Handling Unauthorized API Responses
If the OAuth access token is no longer valid, Salesforce will return a 401 (Unauthorized) status code. Whenever this happens I want to log the user out and require them to re-authenticate so a new access token is granted. I could trap for the 401 status in each API call, but Angular provides a better mechanism for dealing with cross-cutting boilerplate logic for $http calls in the form of $httpInterceptors. Interceptors allow global filtering of http requests as they leave the application and inspecting responses as they return. In this case I just implement the responseError
interceptor and redirect to the Aerobatic logout URL if the status is equal to 401. Interceptors are registered in the app config event using the $httpProvider.
Controllers
The app has three different controllers: LoginCtrl
, MainCtrl
, and ContactModalCtrl
. The LoginCtrl
simply exposes the URL for the login button to the view and determines if a session timeout error message should be displayed by detecting the string “error=expired” in the query string.
MainCtrl
The MainCtrl
handles the main page of the application that renders the list of contacts. The Salesforce service is dependency injected into the controller. The loadContacts
function is invoked immediately upon instantiation.
The divideIntoRows
function splits the array of contacts into sets of 3 to support binding to the responsive Bootstrap grid. Creating a new contact or editing an existing contact launches a modal form and transfers control to the ContactModalCtrl
. The $modal service from AngularUI Bootstrap provides a nice mechanism for passing data back and forth. The ContactModalCtrl
takes a dependency named contact
which resolves to the contact passed into the openContactModal
function. In the case of a brand new contact the value is null.
The saved contact is returned back in the modalInstance.result
as a promise that, upon resolving, is inserted at the start of the list. This is consistent with how the Salesforce API orders contacts where the most recently interacted one comes first. Angular will automatically rebind the grid as part of the digest cycle.
ContactModalCtrl
The contactModal.html template binds textboxes to the various attributes of the contact model.
When the form is submitted the saveContact function is invoked which calls either updateContact or createContact on the Salesforce service depending on whether the contact model has an Id attribute. If the operation succeeds the contact model is passed back to the MainCtrl
in the $modalInstance.close
call.
Normally I would do some error handling on the client, but I intentionally left that out in order to demonstrate how to handle errors returned by the REST API. Obviously the API can’t trust that the data it receives is valid. If the API receives invalid data it will return a 400 (Bad Request) response status along with a JSON array of error objects in the response body:
If the promise is rejected, the error callback extracts the message attribute from each error and binds to a list at the top of the form:
Unit Testing
One area where Angular really shines is unit testing. Historically achieving good browser unit test coverage of Javascript heavy apps was not easily or commonly done. In a single page app where all of the application UI logic resides in the browser, it’s imperative to have an automated test suite or the code will quickly become a maintenance nightmare. The team behind Angular built the open source Karma test runner that, not surprisingly, is the de-facto test framework for Angular apps. With the grunt-karma module, we can embed the karma config settings directly in the Gruntfile which helps keep things tidy.
The angular-mocks library is important to include as it provides necessary helper functions for injecting mock service dependencies to isolate tests from external resources and to allow testing of one module at a time.
Controller Spec
For testing the MainCtrl
I want to use a mock Salesforce service. This turned out to be a bit trickier than anticipated but this blog post ultimately provided the breakthrough.
The trick here is to force a digest cycle to occur using the scope.$root.$digest();
line so that code that executes as part of the promise callback will have completed before making assertions.
Service Spec
Unit testing the API calls in the Salesforce service entails using the $httpBackend to fake out the actual network calls. The $httpBackend includes several expectXXX
style functions that verify specific network calls were made matching on the url, verb, headers, and request body. If a match is made, a canned response is returned.
Between service and controller unit tests, it’s possible to achieve a high degree of coverage of an Angular codebase providing the same quality and maintainability benefits developers have long been accustomed to with server-side development frameworks. Because Javascript is loosely typed, unit tests can also catch coding errors like misspellings that would get caught by a compiler in a strongly typed language. It’s also possible to take testing to the next level with e2e (end to end) tests using another tool built by the Angular team called Protractor. Rather than testing a single module in isolation, Protractor tests execute an entire flow in the browser just as an actual user would interact with the app.
Conclusion
While this sample app doesn’t do anything you can’t already do within Salesforce itself, the main point was to demonstrate how to consume the Salesforce REST API within an HTML5 single page application and how Angular is a great framework with which to build it. Be sure to check out my previous blog post that goes into greater depth on the different security considerations, including how to setup OAuth and proxy API calls. Hopefully this provides a foundation on which you can build more interesting Salesforce apps that tie together data from multiple services.
Finally I’d encourage you to check out Aerobatic to see how a hosting platform that is purpose built for single page applications can augment your app delivery with enhanced security, performance, and workflow optimizations. Please feel free to reach out to me at david@aerobatic.io with questions, feedback, or to discuss a particular use case
Happy coding!