Prototype Angular UIs Without A Backend

September 16, 2013 by Doug Turnbull | javascript, Rapid Prototyping
vectors are fun

So you’ve got an AngularJS UI built out, but you’ll need a fleshed-out backend before being able to really take it for a test drive, right? Actually, it turns out, with the magic of Angular and its mocked $httpBackend, we don’t need no stinking backend!

If you’ve heard of $httpBackend, you’ve probably heard of it it terms of writing unit tests. Tests that look like:

    // tell http backend what to do when in gets a GET request at a specific URL
    $httpBackend.when(“GET”, “/users/4”).respond( {userName: “Doug”, userId: 4} );
    // Code we’re testing
    userService.getUserById(4);
    // flush all pending requests (pretend the server just got back with response)
    $httpBackend.flush();
    expect(userService.getUser(4)).toBe( {userName: “Doug”, userId: 4});

While unit testing like this can help test and play with bits of code, it can’t drive an overall vision the way that seeing, clicking, and typing into a prototype can. Therefore, I’ve taken $httpBackend and implemented a complete mock-up of my backend to allow me to use my UI and iterate quickly. Basically, I can now simply open file://path/to/my/project/index.html in my browser use my application as if it were backed by a database on a server. (That is as long as I don’t fully reload the page:) ).

How have I done this? Read along with the corresponding jsfiddle

First, I tell Angular to use the angular-mocks $httpBackend (by passing it the $httpBackend constructor function) as a decorator on top of the concrete $httpBackend service. In Angular’s dependency injection, a decorator wraps the original service, layering on some custom functionality. In this case, Angular’s mock $httpBackend is setup to receive the concrete $httpBackend service, and will pass through to it if you use the passThrough() function after creating a rule (examples further down). In other words, I reserve the ability to pass some requests along as real HTTP requests.

   myApp
   .config(function($provide) { 
      $provide.decorator('$httpBackend', createHttpBackendMock); 
   });

All our code to mock the backend will take place in this function we execute before running the angular application:

    myApp.run(function($httpBackend, $timeout) {
            … <the cool stuff you’ll see below>
        });

In this run function, I can use the mock $httpBackend API to specify some rules, with actions to perform on the receipt of HTTP requests:

    // Some state
    var users = {};
    var userId = 0;
    $httpBackend.when('PUT', '/users')
    .respond(function(method, url, data) {
      data = angular.fromJson(data);
      users[userId] = {userName: data.userName, userId: userId};
      userId++;
      return [200, users[data.userId]];
    });

I can then specify some rules based on a regex if need be:

    var regexpUrl = function(regexp) {
      return {
        test: function(url) {
          this.matches = url.match(regexp);
          return this.matches && this.matches.length > 0;
        }
      };
    };

    $httpBackend.when('GET', regexpUrl(/users\/(\d+)/))
    .respond(function(method, url, data) {
      data = angular.fromJson(data);
      return [200, users[data.userId]];
    });

And since I’m using a delegate, I can decide that certain requests will just get passed through to do real requests (say JSONP requests to solr from my instant search directive!):

    $httpBackend.when('JSONP', regexpUrl(/http:\/\/.*/))
      .passThrough();

$httpBackend’s way of simulating an asynchronous response is for you to call flush() from your tests. So none of our app’s requests will be responded to until we tell $httpBackend to flush. To create a kind-of $flush run-loop, we’ll use the angular $timeout service to call flush every half-second. $httpBackend still wants to be used in a unit testing context, so it will throw an exception if there’s nothing to flush. So we catch and discard that exception.

    var flushBackend = function() {
      try {
        $httpBackend.flush();
      } catch (err) {
      // ignore that there's nothing to flush
      }
      $timeout(flushBackend, 500 /*ms*/);
   };
   $timeout(flushBackend, 500);

Viola, now I can experiment with my UI much more efficiently! Compared to other solutions, like mocking individual angular services or creating a mockable backend service that wraps the $http calls, I have found this solution the cleanest and least imposing on my production code.

Have you had to solve a similar problem? Please comment and let me know! Also, do you need help with rich search & discovery oriented user interfaces? Let us know. We know quite a bit about building rich UIs for Solr!

7 Comments

Daniel Lidström on September 28, 2013

Great article! This will certainly help when demoing angularjs. Thanks!

keniled on September 28, 2013

Hi there. It's not clear to me why you define a custom function that tests a regexp if that can be done without defining a custom function, like so: $httpBackend.whenPOST(/\/events\/\d+\/arrangements\/\d+\/items$/) Can you please clarify what is the advantage of your method?

Doug Turnbull on September 30, 2013

@kenild I am aware of that feature, but for some reason I just could not get it to work. If you want to fork my jsfiddle and send it back to me, please do so I can see what I was doing wrong.

Niklas Rasmusson on September 30, 2013

Nice stuff, this is just what I need when I'm debugging IE8-9 since they don't support CORS with XMLHttpRequest. I have a question though, how would I respond with a local static .json? When I try return [200, '/path/to/data.json']; it actually returns a text string.

Alex Vanston on November 8, 2013

Hey, this is an awesome tip, thanks for the suggestion. With regard to 'flushing', in Angular 1.2.x, $http requests aren't actually sent until the end of a $digest cycle, so it might make your mocks even more accurate/helpful to bind to $rootScope.$watch('', flushFn)

Failpunk on March 24, 2014

I can't read any of the code snipits on my phone.

lewis on June 9, 2014

this is probably easier/quicker... https://github.com/typicode/json-server setup one big json file, run json-server... job done