Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding dependency at runtime in AngularJS

I have a service called $doggedHttp, which exposes the same interface as $http. Now I want to create a $doggedResource service which is the angular $resource service on top of $doggedHttp instead of $http. In other word I want to inject $doggedHttp as the $http service.

Also, in my application I want to be able to create both $doggedResource and $resource. Thus I cannot simply override $http with $doggedHttp.

I thought dependency injection should make this scenario easy to solve. Am I wrong ?

Instead I had to go deep into the angular source code to finally came up with a quite ugly solution :

angular.module('doggedResource', ['ngResource', 'doggedHttp'])
  .config(function() {
    var ngResource = angular.module('ngResource'),
        doggedResource = angular.module('doggedResource');

    // replace the placeholder below with the $resource factory from ngResource
    doggedResource._invokeQueue[1][2][1][2] = ngResource._invokeQueue[0][2][1][2];
})
.factory('$doggedResource', ['$doggedHttp', '$parse', null /* this is just a placeholder */]);

Is there a better solution ?


Remark that we cannot use $provide.decorator to replace the injected $http service. To illustrate the problem, here are the relevant parts of angular-resource.js :

angular.module('ngResource', ['ng']).
  factory('$resource', ['$http', '$parse', function($http, $parse) {

    function ResourceFactory(url, paramDefaults, actions) {
    }

    return ResourceFactory;
  }

Looking at the code above, the $provide.decorator callback will be passed ResourceFactory as an argument. At that time the dependency $http has already been resolved. And since ResourceFactory use $http inside a closure we cannot change it.

.config(function($provide) {
  $provide.decorator( '$resource', [ "$delegate", function( $delegate ) {
    // here $delegate is the ResourceFactory which has 
    // already been linked to `$http` by a closure.
  }
}
like image 487
recamshak Avatar asked Nov 11 '22 20:11

recamshak


1 Answers

You should probably write all the logic that is there in $doggedHttp in a decorator for $http. Once you decorate $http, everything should work fine

EDIT : Correction for condition.

.config(function($provide) {
  $provide.decorator( '$http', [ "$delegate", function( $delegate ) {
    // here $delegate is the $http function.
    function $doggedHttp(config){
        //write your storage logic here.

        // route all the $http calls through $delegate at the end... 
        return $delegate(config);
    }
    //dont forget to create shortcut method overrides.
    //createShortMethods('get', 'delete', 'head', 'jsonp');
    //createShortMethodsWithData('post', 'put');

    // This is the simplest solution to what you wish to do..
    if( condition ) {
         return $doggedHttp; 
    }
    else { 
         return $delegate;
    }     

    //finally return the $doggedHttp ( and not the $delegate ) 

  }
}

Alternately, you can write all your storage logic in a request interceptor - You can inject anything and everything in there as well, so storing your calls and re-requesting can also be done at that stage.

like image 65
ganaraj Avatar answered Nov 14 '22 23:11

ganaraj