angular-http-auth with $http transformResponse

2.1k views Asked by At

I'm using angular-http-auth to show a login dialog whenever a 401 "unauthorized" response is returned from the server.

Since I'm cool, I also try to deserialize response objects in my services. For example, if a service requests a car and the response is {make: Honda, model: Civic}, I try to deserialize that into a Car object using transformResponse.

For example:

getCar: function() {
    return $http.get('/api/car', {
        method: 'GET',
        transformResponse: function(data, headers) {
            var c = angular.fromJson(data);
            return new Car(c);
        }
    });
}

This doesn't work with angular-http-auth. If the response was a 401 Unauthorized, you'll get a javascript error. It's because angular will try to run that transformResponse code even if the response was a 401.

It turns out that $http interceptors (which is what angular-http-auth uses) are run AFTER the transformResponse code. That's a huge problem, because none of that code in transformResponse will work if the server response was a 401 (there wouldn't be any data)

Is this a problem for anyone else? How did you get around it? Am I not to use transformResponse if I use $http interceptors?

2

There are 2 answers

0
Dennis Hackethal On BEST ANSWER

Late to the party, I know, but to anyone coming here from Google like I did (I also posted this as a comment on a related issue filed with the Angular repo):

I also found it to be confusing that response interceptors run after the transformResponse method. I added a method to $http.defaults.transformResponse. Here is an example from the documentation on how to do that.

So, if you need to basically have a response interceptor that runs before the transformResponse method, this should do it:

'use strict';

angular.module('app')
  .run(function ($http) {
    $http.defaults.transformResponse.push(function (data, headers) {
      // do stuff here before the response transformation

      // Be sure to return `data` so that the next function in the queue can use it.
      // Your services won't load otherwise!
      return data;
    });
  });

If your services or http calls don't have their own response transformer, you're good now.

If your services do have their own transformResponse method, they will actually override all default transformers (I found this out after a long read of the documentation), and the above code will not run.

To circumvent this, you can follow this example in the docs.

0
Chris Clark On

To get around this problem I don't use transformResponse anymore. I just can't see the point of transformResponse if it runs before $http interceptors.

In order to use angular-http-auth and also deserialize responses in your services, you can write your services so that they execute the HTTP request first and then deserialize the response in a callback function.

As an example, here is how I would have restructured the example in the OP:

Plunker

services.factory('HttpCarService', function($resource, $q) {
  var resource = $resource('/api/car');

  return {

    getCar: function() {

      var deferred = $q.defer();
      var car = null;

      var successCallback = function(data, status, headers, config) {
        var c = angular.fromJson(data);
        car = new Car(c);
        deferred.resolve(car);
      };

      var errorCallback = function(data, status, headers, config) {
        deferred.reject("something wrong");
      };

      var result = resource.get(successCallback, errorCallback);
      return deferred.promise;
    }
  };

});

This pattern will also work if data is an array.

$http interceptors will run before either of the callback methods are executed. If your $resource needs url params, you can make the getCar() function accept a config object as a parameter, and pass the necessary information on when you make the $resource.get() call.