Passing configuration to Angular

This is something that we got wrong on our project initially, then had a guy named Mark come on board and nail a very clean solution.

What we were trying to accomplish: we wanted to give our Angular Singe Page App some configuration data for initialization. Things like a CSRF token and API URL, so not necessarily things we could load from a web service.

The wrong way to do it:

We started off using an ng-init on our element. If you RTFM on ng-init they make it very clear that you should not be using it for that purpose. In our defense, the name “init” is right there in the name and the warning wasn’t as bright red in earlier versions of the documentation.

A better way to do it:

What we are doing now is putting this in our server-side template:

<script>
angular.module('ns-config', [])
    .constant('config', {{config|js}});
</script>

and then inject the ns-config module into our project. By using Angular’s constant() instead of value() the config object is available in our application’s .config() block.

Angular’s $http.error()

Earlier I promised (ha! PROMISE!) to explain why I don’t like Angular’s $http.success() and .error() but this guy beat me to the punch:

Don’t use $http’s .success()

First, I hate anything that creates inconsistency in my code. If we use success() we’ll still have to use then() in every other place that has a promise, which makes this syntax sugar more of a cognitive leak in my opinion than anything useful.

Second, it couples your code in a subtle way – you’re telling everyone you’re actually reaching to the network to get something.

These are big issues but I think it misses the biggest one: you lose most of the Power of Promises™ by using .success().

Promises are amazing. You can use them to make async code reasonable. You can use them to compose small async bits into a functioning whole. And $http.success() throws it all away. Take a look at this code:

app.controller('MainCtrl', MainCtrl);
function MainCtrl($scope, $http) {
  $scope.started = 'Started';
  $http
    .get('http://date.jsontest.com/')
    .success(function(resp) {
      $scope.time = resp.time;
      return $http.get('http://ip.jsontest.com/');
    })
    .success(function(resp) {
      $scope.ip = resp.ip;
    })
    .finally(function() {
      $scope.finished = 'All done';
    });
}

See the issue? Here it is on Plunker – the IP address isn’t getting filled in. Why? Because you can’t chain HttpPromises together like you can real Promises. What’s actually happening on the second .success() is that it’s calling the Date service a second time! If you were reading that code would you expect 2 calls to the Date service? Here’s the same code using .then():

app.controller('MainCtrl', MainCtrl);

function MainCtrl($scope, $http) {
  $scope.started = 'Started';
  $http
    .get('http://date.jsontest.com/')
    .then(function(resp) {
      $scope.time = resp.data.time;
      return $http.get('http://ip.jsontest.com/');
    })
    .then(function(resp) {
      $scope.ip = resp.data.ip;
    })
    .finally(function() {
      $scope.finished = 'All done';
    });
}

That actually works like you would want it to. It works how Promises are supposed to work: if your .then() returns a Promise it will pass the resolved value to the next .then(). I’m not even getting into error testing. And if you were to pass that to some other system they could add to the chain however they wanted and it would Just Work™.

Then (ha! THEN!) there’s the issue of interoperability. Promises are in ES6 and anything that supports the Promises/A+ API should work together. That means Angular can create a Promise that Bluebird can wrap it in a timeout. Want to split 1 $http call into 5 $http micro-service calls because that’s the Hot New Architecture? If you were using .then() you could just wrap your calls in $q.all(), but $q.all() doesn’t have a .success() method. You lose all that power if you’re calling .success() and .error() all over the place.

So please please please stop using .success() and .error() in your Angular projects and stick to POPO: Plain ‘Ol Promise Objects.

RESTful responses and Angular

Testing for an empty JSON document

I have a RESTful service that performs a search and returns the results as JSON, and if there’s no results I return an empty document, {}.

Now I don’t know what the original author’s situation is or how much control they have over the API they’re consuming, but I can definitely project my own experiences on his blog post. At the link above he provides is a good example of the right solution to the wrong problem. It handles the response given, but the response itself is the real problem.

When you are using a RESTful web service to search and it cannot find anything, you shouldn’t wind up in this situation to start with. The fix isn’t better client code, the fix is a better web service.

When your service does a search without finding anything, it should return 404 Not Found. I’ve had to deal with too many web services that only had 2 response codes: 500 if there was an uncaught exception or 200 OK for everything else. There is a sea of other HTTP response codes and limiting yourself to just those two limits the usefulness of your service.

If Angular gets a 404 response it won’t invoke the .success() block, it will go straight to .error() handler. (.success() and .error() have their own issues but that’s another blog post) That would have prevented this situation in the first place. In my project at work we are doing just this: a search that doesn’t find anything returns a 404. When people have looked at our network traffic they have complained to us about these errors, but by sticking to HTTP semantics we’ve avoided this all together.

“But it shouldn’t return an error code if the service executes without errors!” – The 400 series of status codes is for client errors, requests that can’t be fulfilled. If I request a URL that doesn’t have a file on a static website, I get a 404. Why should it be different if I request an ID that doesn’t exist in your database? Why should a search for something that doesn’t exist and may never have been OK?

“But I don’t want errors in my code!” – When you add RESTful web services to your architecture, you are making HTTP part of your execution flow. A 404 response is kind of like an exception: you don’t want to hide it from your client any more than you would want to catch all exceptions. Let the client decide what to do when nothing is found, don’t try to pretend everything is OK.

So please, if you’re exposing a web service over HTTP use Status Codes correctly. Or don’t, it gives me something to blog about.