Search code examples
javascriptangularjsangular-providers

Angular provider configuration reset when service injected into somewhere else


Not an easy one to describe this, but basically I have a service which is set up as a provider so I can configure it. It has an array of APIs used in my project which is initially empty. Various config blocks can add an API object to the array and this seems to be working. I console.log the output each time and the array is growing.

I then inject my service into something else (in this case an $http interceptor function) and use a service method to return the array, but each time I get an empty array.

I thought the way this worked is that all config blocks ran first and that the $http call which is being intercepted is happening way after that, so the array should be full of APIs by the time it's intercepted.

Anyway, here's some code

angular.module('authModule').provider('authService', function(){

    var _apis = [];

    return({
        addApi: addApi,
        $get: instantiateAuth
    });

    function addApi(newApi){
        _apis.push(newApi);
        console.log("API added", _apis);
    }

    function instantiateAuth() {
        return({
            getApis: function(){
                console.log("Getting APIs", _apis);
                return _apis;
            }
        });
    }

})


.config(function($httpProvider){

    $httpProvider.interceptors.push(function($log, $rootScope, $q) {
        return {
            request: function(config) {
                var injector = angular.injector(['ng', 'authModule']);
                var authService = injector.get('authService');
                console.log("apis", authService.getApis());
            }
        };
    });

});

And an example config block

angular.module('myModule').config(function ($provide, authServiceProvider) {

    authServiceProvider.addApi({
        url: 'https://apiurl.com',
        other: 'stuff'
    });

    authServiceProvider.addApi({
        url: 'https://apiurl2.com',
        other: 'stuff'
    });

});

So, each time the appApi method is called in a config block (twice here), this line outputs the array console.log("API added", _apis); and it correctly outputs 1 item after the first call and two items after the second call.

When this code - authService.getApis() - fires the first time an HTTP call is intercepted, it logs an empty array to the console.

Any help would really be appreciated.

EDIT:

The problem seems to be this line

var injector = angular.injector(['ng', 'authModule']);

My provider seems to be reset/recreated each time this happens, so maybe I'm misunderstanding how to use the injector. I was originally just injecting my authService the normal way in the function parameters but I was getting a circular dependency (my auth service needs to open a modal window, but angular-ui modals rely on the http service and my http calls are being intercepted to check with my auth service that the user is authenticated :( )


Solution

  • Yes, angular.injector(['ng', 'authModule']) essentially creates a new injector instance (an application instance, in layman's terms):

    angular.injector(['authModule']) !== `angular.injector(['authModule'])
    

    ng module is loaded by default, it doesn't have to be specified explicitly. And singleton services are singletons only within the same injector instance:

    injector.get('authService') === injector.get('authService');
    

    But

    angular.injector(['authModule']).get('authService') !== `angular.injector(['authModule']).get('authService')
    

    To reuse current injector instance (which is a desirable behaviour in almost every situation) $injector service should be used:

    $httpProvider.interceptors.push(function($log, $rootScope, $q, $injector) {
        return {
            request: function(config) {
                var authService = $injector.get('authService');
            }
        };
    });
    

    $injector.get is known and straightforward solution to get around circular dependencies.