Building a Single Page App with AngularJS and the Salesforce REST API

Learn how single-page apps (SPAs) can dramatically improve responsiveness on mobile devices, and how to build them using JavaScript, HTML5, CSS and the REST API.

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.

├── Gruntfile.js
├── README.md
├── bower.json
├── bower_components
├── css
├── index.html
├── js
│   ├── app.js
│   ├── controllers
│   ├── directives
│   └── services
├── login.html
├── package.json
├── partials
└── test
    ├── fixtures.js
    └── spec
        ├── controllers
        └── services

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.

ngAnnotate: {
  target: {
    files: {
      'tmp/annotated.js': ['js/**/*.js']
    }
  }
}

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.

aerobatic: {
  deploy: {
    src: ['index.html', 'login.html', 'dist/*.*', 'favicons/*', 'partials/*.html', 'images/*.*'],
  },
  sim: {
    index: 'index.html',
    login: 'login.html',
    protocol: 'https',
    port: 3000,
    livereload: true
  }
}

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.

function buildHttpConfig(url, options) {
  var config = _.defaults(options || {}, {
    method: 'GET',
    headers: {}
  });

  if (_.contains(['POST', 'PATCH'], config.method))
    config.headers['Content-Type'] = 'application/json; charset=UTF-8';

  // The token user.accessToken will get replaced by Aerobatic
  // with the actual access_token that came back in the
  // OAuth callback. Aerobatic intentionally keeps this
  // token securely stored on the server and avoids passing
  // it over the network.
  config.headers['X-Authorization'] = 'OAuth @@user.accessToken@@';

  config.headers.Accept = 'application/json';

  // Wrap the url in a call to the API proxy
  config.url = '/proxy?url=' + encodeURIComponent(url);

  return config;
}

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.

salesforce.loadContacts = function() {
  var soql = "SELECT Id, FirstName, LastName, Title, Phone, Email FROM Contact";
  var url = apiEndpoint + "/query?q=" + encodeURIComponent(soql);

  var deferred = $q.defer();
  $http(buildHttpConfig(url)).success(function(data, status) {
    deferred.resolve(data);
  }).error(function(err, status) {
    deferred.reject(err);
  });
  return deferred.promise;
};

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.

salesforce.createContact = function(contact) {
  var url = apiEndpoint + '/sobjects/Contact/';

  var deferred = $q.defer();
  $http(buildHttpConfig(url, {method: 'POST', data: contact})).success(function(data) {
    // Assign the new Id to the contact
    contact.Id = data.id;
    deferred.resolve(contact);
  }).error(function(err, status) {
    deferred.reject(err);
  });

  return deferred.promise;
};

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.

salesforce.updateContact = function(contact) {
  var url = apiEndpoint + '/sobjects/Contact/' + contact.Id;

  var deferred = $q.defer();
  $http(buildHttpConfig(url, {method: 'PATCH', data: _.omit(contact, 'Id')})).success(function(data) {
    deferred.resolve(contact);
  }).error(function(err) {
    deferred.reject(err);
  });

  return deferred.promise;
};

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.

$httpProvider.interceptors.push(function($q, $window, aerobatic) {
  return {
    responseError: function(rejection) {
      var status = rejection.status;
      // If the status is 401 Unauthorized, automatically logout
      if (status == 401) {
        $window.location = aerobatic.logoutUrl + (aerobatic.logoutUrl.indexOf('?') == -1 ? '?' : '&') + 'error=expired';
        return;
      }
      return $q.reject(rejection);
    }
  };
});

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.

var contacts;
$scope.contactsLoading = true;
$scope.filterText = '';

Salesforce.loadContacts().then(function(data) {
  $log.info("Salesforce returned " + data.records.length + " contacts");
  $scope.contactsLoading = false;
  contacts = data.records;
  divideIntoRows(contacts);
}, function(data) {
  $scope.contactsLoading = false;
  // TODO: Show an error message in the view
  $log.error(data);
});

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.

$scope.openContactModal = function(contact) {
  var modalInstance = $modal.open({
    templateUrl: aerobatic.cdnUrl + '/partials/contactModal.html',
    controller: 'ContactModalCtrl',
    size: 'lg',
    resolve: {
      contact: function () {
        return contact;
      }
    }
  });

  modalInstance.result.then(function(savedContact) {
    // If we are updating an existing contact, remove it from
    // the array.
    if (contact)
      contacts = _.reject(contacts, {Id: contact.Id});

    // Put the new contact in the first slot which matches
    // how the Salesforce list contacts API works
    contacts.unshift(savedContact);
    divideIntoRows(contacts);
  });
};

ContactModalCtrl

The contactModal.html template binds textboxes to the various attributes of the contact model.

<form class="form-horizontal" ng-submit="saveContact($event)">
  <div class="modal-body">
    <div class="row">
      <div class="col-md-6">
        <label for="firstName">First Name</label>
        <input type="text" class="form-control" name="firstName" ng-model="contact.FirstName" autocomplete="off">
      </div>
      <div class="col-md-6">
        <label for="lastName">Last Name</label>
        <input type="text" class="form-control" name="lastName" ng-model="contact.LastName" autocomplete="off">
      </div>
    </div>
  </div>
  <div class="modal-footer">
    <i class="fa fa-spinner fa-spin fa-2x" ng-show="contactSaving"></i>
    <button class="btn btn-primary" type="submit" ng-disabled="contactSaving">Save Contact</button>
    <button class="btn btn-warning" type="button" ng-click="modalInstance.dismiss('cancel')">Cancel</button>
  </div>
</form>

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.

angular.module('controllers').controller('ContactModalCtrl', function(
    $scope, $log, $modalInstance, aerobatic, Salesforce, contact) {

  // Clone the contact so that any edits are not reflected in the
  // main page.
  $scope.contact = (contact ? _.clone(contact) : {});
  $scope.modalInstance = $modalInstance;

  $scope.saveContact = function(evnt) {
    $log.debug("Saving contact");
    delete $scope.errors;
    $scope.contactSaving = true;

    var operation = _.has($scope.contact, 'Id') ?
      Salesforce.updateContact : Salesforce.createContact;

    operation($scope.contact).then(function(contact) {
      $scope.contactSaving = false;
      $log.debug("Contact saved successfully");
      $modalInstance.close(contact);
    }, function(err) {
      $scope.contactSaving = false;
      $log.error("Error returned from Salesforce API: " + JSON.stringify(err));
      $scope.errors = _.map(err, 'message');
    });

    evnt.preventDefault();
  };
});

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:

[{
  "message":"Email: invalid email address: sdfgdfg",
  "errorCode":"INVALID_EMAIL_ADDRESS",
  "fields":["Email"]
}]

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:

<!-- Used to display errors returned by the API. -->
<div class="alert alert-danger" role="alert" ng-show="errors">
  <ul>
    <li ng-repeat="error in errors">{{error}}</li>
  </ul>
</div>

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.

karma: {
  options: {
    files: [
      'http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js',
      'http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.min.js',
      'http://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular-mocks.js',
      'test/fixtures.js',
      'js/**/*.js',
      'test/spec/**/*.js'
    ],
    frameworks: ['jasmine'],
    browsers: ['Chrome'],
    logLevel: 'INFO',
    plugins : [
      'karma-jasmine',
      'karma-chrome-launcher'
    ],
    reporters: 'dots'
  },
  unit: {
    singleRun: true
  }
}

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.

// Spec for the mainCtrl
describe("mainCtrl", function() {
  var scope, ctrl, salesforceMock, loadContactsDeferred;

  beforeEach(module('controllers'));

  beforeEach(inject(function($rootScope, $controller, $q) {
    salesforceMock = {
      loadContacts: function() {
        loadContactsDeferred = $q.defer();
        return loadContactsDeferred.promise;
      }
    };

    spyOn(salesforceMock, 'loadContacts').and.callThrough();

    scope = $rootScope.$new();
    controller = $controller('MainCtrl', {
      $scope: scope,
      $modal: { open: function(){}},
      aerobatic: {},
      Salesforce: salesforceMock
    });
  }));

  it('Salesforce.loadContacts is called', function() {
    loadContactsDeferred.resolve({records: sampleContacts});
    scope.$root.$digest();

    expect(salesforceMock.loadContacts).toHaveBeenCalled();
    expect(scope.contactRows.length).toEqual(2);
  });

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.

describe("Salesforce", function() {
  var httpBackend, salesforce;

  var expectedHeaders = {
    'Content-Type': 'application/json; charset=UTF-8',
    'X-Authorization': 'OAuth @@user.accessToken@@',
    Accept: 'application/json'
  };

  beforeEach(inject(function($httpBackend, Salesforce) {
    // Set up the mock http service responses
    httpBackend = $httpBackend;
    salesforce = Salesforce;
  }));

  afterEach(function() {
    httpBackend.verifyNoOutstandingExpectation();
    httpBackend.verifyNoOutstandingRequest();
  });

  it('makes GET request to fetch contacts', function() {
    httpBackend.expectGET(/^\/proxy/, _.omit(expectedHeaders, 'Content-Type')).respond({ records: sampleContacts});

    var result;
    salesforce.loadContacts().then(function(data) {
      result = data;
    });

    httpBackend.flush();
    expect(result).toEqual({records: sampleContacts});
  });

  it('makes POST request to create a contact', function() {
    var contact = {
      FirstName: 'Chuck',
      LastName: 'Yeager'
    };

    httpBackend.expectPOST(/^\/proxy/, JSON.stringify(contact), expectedHeaders).respond({ id: '5'});

    salesforce.createContact(contact);
    httpBackend.flush();
    expect(contact.Id).toEqual('5');
  });
});

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!

 

 

Published
July 31, 2014

Leave your comments...

Building a Single Page App with AngularJS and the Salesforce REST API