javascriptangularjsdependency-injectionangular-factory

How to change Angular factory to resolve before injection


I have an existing application that uses a MapProvider like so:

mapModule.factory('MapProvider', ['$injector', function($injector) {
    return $injector.get('GoogleMapsService');        
}]);

This MapProvider is used extensively across the application and is injected into various other controllers and services (rightly or wrongly).

I now need to add a BaiduMapsService, which I have been able to get working as a test with:

mapModule.factory('MapProvider', ['$injector', function($injector) {
    if(true) {
        return $injector.get('GoogleMapsService');
    } else {
        return $injector.get('BaiduMapsService');
    }
}]);

And flipping the if value accordingly. (Both of these services are using a TypeScript interface, so have the same methods). Now, I need to add a $http call to the API, which will return which map to use, based on the provided data. How can I make my factory asynchronous, without having to change all my MapProvider.someCallHere() calls to MapProvider.then(m => m.someCallHere()).

Ideally, when MapProvider is injected across my application, it will be able to resolve using the async data (only once), and then inject the necessary service afterwards.

Alternatively, is there a way to defer / delay loading Angular at all, until I make an API call and set some global data somewhere?

Thanks.


Solution

  • You can postpone the application bootstrap (also, don't use ng-app, do it manually) until you get data from server. I've answered this before on this question but each case has its own specific details.

    I usually see a config value being declared on the app before the application gets bootstraped, this is very useful for multi-tenant apps. So that this preference values can be used in the whole app as an injected provider.

    For example:

    var app = angular.module('app', []);
    
    // retrieve the $http provider
    var ngInjector = angular.injector(["ng"]);
    var $http = ngInjector.get("$http");
    
    // load config function. Returns a promise.
    function loadConfig(){
        return $http.get("/config.json").then(function(response) {
            // declare the configuration value on your app
            app.constant("Config", response.data);
        }, function(err) {
            console.error("Error loading the application config.", err)
        });
    }
    
    // Call loadConfig then bootstrap the app
    loadConfig().then(function () {
        angular.element(document).ready(function() {
            angular.bootstrap(document, ["app"]);
        });
    });
    

    Finally from your factory, you can use the Config constant to retrieve the preferred map.

    mapModule.factory('MapProvider', ['$injector', 'Config', function($injector, Config) {
        if(Config.preferedMap == 'GoogleMap') {
            return $injector.get('GoogleMapsService');
        } else {
            return $injector.get('BaiduMapsService');
        }
    }]);