Is storing variables in $rootScope good practice?

All we need is an easy explanation of the problem, so here it is.

Update 2: I found a solution

I’ve altered the settings file to just a JS file, added var tempSettings = to the beginning of the file, and added it in index.html. This way it gets loaded with the initial HTML, ensuring it will exist when app.run goes. The settings service then takes this tempSettings variable and puts it in the service. To clean up, I remove the tempSettings pointer.

New settings file called settings.js

var tempSettings = {
  "environment": "development",
[...]

Added to index.html:

<script src="settings.js"></script>

Service:

myApp.service("settings", function(){
  var settings = null;

  this.initialize = function() {
    settings = tempSettings;
    tempSettings = undefined;
  };

  this.get = function() {
    return settings;
  }

});

Update 1: I found a problem

Because the settings file is loaded async, it sometimes happens that a module tries to use the settings before they are loaded. I’ll keep you updated on solutions. I have moved the settings into a service, that is definitely better.

Original question

When I google how to store environment settings in AngularJS apps, I come across options using Grunt or Gulp (and there’s probably others as well), but to me this option seems much more obvious. Meaning that there’s probably a good reason not to use it. Is this way of storing settings a bad idea?

I have in my app root a file called settings.json which looks something like this:

{
  "settingsFile": true,
  "environment": "development",
  "logLevel": "debug",
  "userApiBase": "http://localhost/covlelogin/web/api/",
  "oAuth": {
    "google":{
      "endpoint": "https://accounts.google.com/o/oauth2/auth",
      "clientId": "12345",
      "scope": "email profile",
      "state": "MyToken123",
      "redirectUri": "http://localhost/loginadmin/web/oAuthRedirect",
      "responseType": "code",
      "approvalPrompt": "force"
    }
  }
}

I then have a little bit in app.run that looks like this:

MyApp.run(function ($rootScope, $http) {
  //Load settings
  $http.get('settings.json').
      success(function (settings) {
        if (settings.settingsFile){
          $rootScope.settings = settings;
          console.log("Settings loaded");
        }else{
          console.log("Error loading settings. File may be corrupt.");
          //Additional error handling
        }
      }).
      error(function (data) {
        console.log("Error getting settings file.");
        //Additional error handling
      })
});

And now whenever I need a setting I can always go to $rootScope.settings.userApiBase or whatever. It makes sense to me, because all I have to do is to make sure settings.json is ignored on check-in. The whole method is really straight forward. Is there a flaw to this design?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Try not to pollute the $rootScope Here as much as possible. Make a Settings service that handles the settings instead. Services are singleton objects so once you initialize the service, the settings will be available everywhere you inject the service.

MyApp.service("Settings", function($http) {

    var settings = null;

    this.initialize = function() {
        $http.get('settings.json').success(function (s) {
            if (s.settingsFile){
                settings = s;
                console.log("Settings loaded");
            } else {
                console.log("Error loading settings. File may be corrupt.");
                //Additional error handling
            }
        }).error(function (data) {
            console.log("Error getting settings file.");
            //Additional error handling
        })
    };

    this.get = function() {
        return settings;
    }

    return this;
});

And in MyApp.run:

MyApp.run(function (Settings) {
    Settings.initialize();
}

Then, whenever you want to access Settings in a controller or another service or something, simply call Settings.get() which will return your settings. Just make sure to inject the Settings service into anything that uses it (like I did in the second code block).

Method 2

In general, you should avoid polluting the rootScope when possible. I like that you are loading the settings in app.run(). What about introducing a SettingsService that is populated in app.run, and can be injected into your other controllers/services? This has the added value of being mockable during unit tests.

Here is a plunker

app.run(function(SettingsService) {
  SettingsService.name = "Alex";
  SettingsService.password = "pw1";
})

app.controller('MainCtrl', function($scope, SettingsService) {
  $scope.settings = SettingsService;
});

app.factory('SettingsService', function() {
  return {}
})

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply