1

Assume i've a route on my app like /config.json which will return an object with settings like user currency, user locale, etc.

I would like to preload all of this values as soon as possible, and only one time, while the application is going up.

The app.config() section would be good enough i think, but i cannot access the $http service at that time. How would you approach this?

EDIT

I'm adding this attempt to provide more context.

module.factory("test", function ($http) {

    // This is the service. It depends on the
    // settings, which must be read with a HTTP GET.
    return service = {
        settings: null,
        get: function(value) {
            return this.settings[ value ];
        }
    };

});

I've tried to return a deffered service instance, i.e.

return $http.get('/config').then(function(response){
    service.settings = response.data;
    return service;
});

but, apparently, it's not allowed to return a deferred service.

brazorf
  • 1,943
  • 2
  • 32
  • 52
  • 1
    This is a great question .. I have had this problem too but don't have solution yet. I am wondering if we could explore having the main module (app) dependent on another angular module that does this pre-fetches (app initialization)? – trk Mar 07 '16 at 20:39
  • @82Tuskers check the accepted answer, that should be the proper way to handle this – brazorf Apr 06 '16 at 11:38

3 Answers3

3

I can think of a few methods to handle this.

  1. If you are using something like php to write your index page, you could write the contents of that file to the index page before the page is processed by the browser.

    <?php 
       //load the contents of the config file
       $configContents = file_get_contents('./config.json');
    ?>
    <html>
      <head>
        <!--all your script and css includes here-->
        <script>
           var configJson = <?php echo $configContents; ?>
           module.constant('config', configJson );
        </script>
        <!-- rest of your page... -->
    
  2. Based on this post, it looks like you can use the injector to get services, and then you can manually bootstrap your application (based on this documentation) after loading the required data. (I haven't tested this...)

    angular.injector(['ng']).get('$http').get('./config.json').then(function(response){
        module.constant('config', response.data);
    
        //manually bootstrap your application now that you have gotten your data
        angular.element(document).ready(function() {
            angular.bootstrap(document, ['myApp']);
        });
    });
    
Community
  • 1
  • 1
TwitchBronBron
  • 2,783
  • 3
  • 21
  • 45
0

Try using app.run() method instead of config, you can inject there

Update: Try using a provider with cache:

app.provider('configurationsProvider', function() {

    this.$get = function($http) {
        if (!this.configurationsPromise){
            this.configurationsPromise = $http.get('/ConfigUrl');
        }

        return this.configurationsPromise;
    };
});

app.service('someService', function(configurations){
    configuration.then(function(conf){
    });
});

Update2:

Any way, you need to assume the setting loading may take some time...So you can go with the approach like this:

app.value('config', { isLoading: true })
   .service('someService', ['config', function('config') { 
        ...
        this.doSomething = function() {
             var someConfig = config.configValue;
        }
    }]);
app.run(['config', '$http', function(config, $http){
    $http.get('configUrl').then(function(conf) {
        _.defaults(config, conf); // or some other way to clone all the properties
        config.isLoading = false;
    });
});

The idea is when the settings are loading the app is not ready yet but you want the user to see something, so when the setting isLoading is true, show some loading bar and when it becomes false show the app... Use ng-if binded to the isLoading to render all the app content when ready and the spinner when not ready

Slava Shpitalny
  • 3,965
  • 2
  • 15
  • 22
  • Nop, too late. Config values are needed from services, so they must be available before the run stage. – brazorf Mar 07 '16 at 18:01
  • Added an update, not sure if this is the final form you should do it but its a way of doing it – Slava Shpitalny Mar 07 '16 at 18:28
  • Still your code has a problem. Think about service X that requires the 'config' service. When X is injected, config will be injected too, but you can't really tell when the http get inside the app.run is done (most likely it will not by the time it's needed). An ideal solution should take into account that the service is, in fact, deferred. – brazorf Mar 07 '16 at 21:00
  • It does take in into the account, that i why the isLoading property is present, the main html element of the app should be binded with ng-if to the isLoading property of the configuration, meaning nothing important of the app will run, and only when the isLoading changes to false the app will appear and the loading will dissapear and all the logic of the app that actually uses the configuration will start running, at this point all the configurations will be present – Slava Shpitalny Mar 07 '16 at 21:03
0

Are the contents of this json file going to be accessed from within your controllers? If so I would recommend creating an api for accessing it in a service that gets passed into every controller that needs it.

Next comes the question of (when do you NEED it). If it's something that can be loaded asynchronously then have the $http.get method within the initialization of the service. If it needs to be loaded up BEFORE the application or controllers load, then maybe consider loading it as part of the page and accessing it through the global scope.

<script src="config.json"></script>

The async method is the prefered method and you should REALLY try that before you start putting it into the global scope.

Update of what the service should look like

.service('ConfigService', function($http) {
    var ConfigService = {};
    ConfigService.config = {};

    ConfigService.load = function() {
        $http.get('/config').then(function(response){
            this.config = response.data
        });
    };
    ConfigService.load();

    return ConfigService;
});

Then from in your controllers you would access ConfigService.config;

cDecker32
  • 813
  • 1
  • 10
  • 20
  • I wouldn't go that way, because they are very static values and i don't want to have an additional, redundant, http get each time the service is needed. – brazorf Mar 07 '16 at 18:03
  • https://docs.angularjs.org/guide/services Take a look at the documentation, controllers don't get their own instances of the service, they get a reference to the single instance that exists within the scope of the entire app. They're the best way to share data between controllers. – cDecker32 Mar 07 '16 at 18:05
  • There comes the problem with the settings being an async value. Please see edit – brazorf Mar 07 '16 at 18:21
  • I updated how to handle this via async. But there is no guarantee that this will finish before your controllers initialize. – cDecker32 Mar 07 '16 at 19:34
  • I've tried a solution like that. The problem with your code is that the ConfigService might be injected and used *before* the http get returns. You can't rely on this.config being assigned before its actually needed. – brazorf Mar 07 '16 at 21:30
  • Right, I specified that if you can't do an async load of the configuration info then maybe consider making the config data a global variable that is access by the data service. – cDecker32 Mar 07 '16 at 21:35
  • Or a http promise chain that initiates the rest of the page after the get returns. – cDecker32 Mar 07 '16 at 21:36