How to Create a Prototype Angular UI without a Back-end
Join the DZone community and get the full member experience.
Join For FreeSo, you’ve got an AngularJS UI built out, but you’ll need a fleshed-out back-end before being able to really take it for a test drive, right? Actually, with the magic of Angular and its mocked $httpBackend, we don’t need no stinking back-end!
If you’ve heard of $httpBackend, you’ve probably heard of it it terms of writing unit tests. The tests look like this:
// 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 to 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 set up 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 along some requests as real HTTP requests.
myApp .config(function($provide) { $provide.decorator('$httpBackend', createHttpBackendMock); });
All of our code to mock the backend will take place in this function, which 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 because 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);
Voila, 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 to be 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- and discovery-oriented user interfaces? Let us know. We know quite a bit about building rich UIs for Solr!
Published at DZone with permission of Doug Turnbull, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments