Inconsistent response types with ngResource in UIWebView

July 14th 2014

While working on a mobile hybrid application, we recently ran into an issue where in some browsers, the correct data was not being returned from our $resource correctly.

var Data = $resource("/data/:id", {}, {
  all: {
    method: "GET",
    responseType: "json",
    isArray: true,
    transformResponse: function(response) {
      return response.names;
    }
  }
});
Data.all().$promise.then(console.log);

In desktop browsers it was giving us the expected array of names. But in iOS 7, it was actually returning just an empty array.

The output in Chrome

{
    "names": [{"id": 1, name: "rokkincat"}, {"id":2, name: "Rockin Catz"}]
}

The output in iOS 7

[]

The difference is subtle, chrome is parsing the data into a JavaScript object, and iOS7 is just passing the responseText through. Then when we try to access the names attribute on response (a string) it gives us undefined. Then since we had isArray set to true, it finaggled undefined into [], which is not helpful. Even more infuriating is that if you are on the developer preview of iOS8, you get the following:

The output in iOS 8

{
  "names": [{"id": 1, name: "rokkincat"}, {"id":2, name: "Rockin Catz"}]
}

The problem comes from a partial implementation of XMLHttpRequest in the webkit bundled in iOS 7, specifically not supporting the responseType property. Angular passes your value for responseType directly to the XHRHttpRequest without any polyfill.

To get around this, you can either forego giving a responseType and just parse the JSON yourself in transformResponse or you can put the following injector on the $http module which will add responseType support to browsers that don’t have it. This code can technically go on any of your modules, but I would suggest putting it near your factory or service that is exposing the $resource.

.config(function($httpProvider) {
  var jsonResponseParser = function(response) {
    if(typeof response === "string") { response =  JSON.parse(response); }
    return response;
  }
  $httpProvider.interceptors.push(function() {
    return {
      request: function(request) {
        if(request.responseType === "json") {
          if(typeof request.transformResponse === "function") {
            request.transformResponse = [jsonResponseParser, request.transformResponse];
          } else {
            request.transformResponse.push(jsonResponseParser);
          }
        }
        return request;
      }
    }
  })
})

Stuck on a Javascript issue in your product?

Javascript is one of our core tech stacks. We can help!


RokkinCat

is a software engineering agency. We build applications and teach businesses how to use new technologies like machine learning and virtual reality. We write about software, business, and business software culture.