Custom AngularJS JSONP callback name

At my work place we are working on some AngularJS projects. We also write unit tests using Jasmine over Karma.

So today I found out a colleague at work used jQuery’s $.getJSON and not Angular’s $http, since he had to make a JSONP request to a service which did not accept function names with dot (.) sign. Apparently Angular is calling its callback functions in JSONP requests in the following structure: angular.callbacks._{id}, which did not work well with the service we tried to use. This solution prevented us from being able to test the call using Angular’s injection mechanism.

I thought to myself there must be a way to tell Angular which callback name structure to use, since I wanted to be able to do it the Angular way and not use jQuery unless it was the only option.

Of course I turned to Google and started searching for a solution and here’s what I found up. It seems that the default name is hard coded in Angular’s code, but eventually I stumbled across this stackoverflow question which held exactly the answer to my problem.

The idea is to register an interceptor in our Angular app config, that all requests done using Angular’s $httpProvider will pass through. In this interceptor we will check if the request type is JSONP and if so we will replace the callback in the URL (the string JSON_CALLBACK) to whatever we want, instead of the original replacement that Angular is doing. In our code example it is 'angular_callbacks_' + callbackId .

I’ll spare you the time and send you straight to the code written in the answer by runTarm on the question page, with the example found on this plnkr page:

var app = angular.module('myApp', [])
  .config(function($httpProvider) {
    $httpProvider.interceptors.push('jsonpInterceptor');
  })

  .controller('MainCtrl', function($scope, $http) {
    $http.jsonp('http://someservice.com?callback=JSON_CALLBACK')
      .then(function(response) {
        console.log(response.data);
      });
  })

  .factory('jsonpInterceptor', function($timeout, $window) {
    return {
      'request': function(config) {
        if (config.method === 'JSONP') {
          var callbackId = angular.callbacks.counter.toString(36);
          config.callbackName = 'angular_callbacks_' + callbackId;
          config.url = config.url.replace('JSON_CALLBACK', config.callbackName);

          $timeout(function() {
            $window[config.callbackName] = angular.callbacks['_' + callbackId];
          }, 0, false);
        }

        return config;
      },

      'response': function(response) {
        var config = response.config;
        if (config.method === 'JSONP') {
          delete $window[config.callbackName]; // cleanup
        }

        return response;
      },

      'responseError': function(rejection) {
        var config = rejection.config;
        if (config.method === 'JSONP') {
          delete $window[config.callbackName]; // cleanup
        }

        return $q.reject(rejection);
      }
    };
  });

I hope this helps you as it helped me.

Comments

  • Thanks, Great solution.
    But the json callback fails on browser back button click. any idea why this happens.

  • Unfortunately won’t work when requesting to resources in parallel. The callback id won’t be incremented in between and the callback of the second request won’t be defined anymore: “Uncaught ReferenceError: angular_callbacks_0 is not defined” (see plnkr.co/edit/TVnta6lZeaAaVC7Z8vBl)

Leave a Reply

Your email address will not be published. Required fields are marked *