toddmo toddmo - 2 months ago 5
AngularJS Question

How to set a variable from an $http call then use it in the rest of the application WITHOUT making the whole application asynchronous

I have this data

{
"config": {
"RESTAPIURL": "http://myserver/myrestsite"
}
}


and I have this factory that reads that data

'use strict';

angular.module('myApp').factory('api',
["$http", "$q",
function ($http, $q) {

function _getConfiguration() {
var deferred = $q.defer();
$http.get('/scripts/constants/config.json')
.success(function (data) {
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject(data, status);
});
return deferred.promise;
}

function _restApiUrl() {
// this doesn't work either. _getConfiguration() doesn't resolve here.
return _getConfiguration().RESTAPIURL + '/api/';
}

return {
URL: _restApiUrl
}
}
]
);


Then to use it

'use strict';

angular.module('myApp').factory('AuthService', function ($http, $q, api,NotificationService) {

function _get(creds) {

var deferred = $q.defer();

$http({method: 'GET', url: api.URL() + api.AUTH, headers: {
'Authorization': 'Basic '+creds}
})
.success(function (data, status, results, headers) {
deferred.resolve(results);
})
.error(function (data, status) {
NotificationService.redirect(status);
deferred.reject(data, status);
});
return deferred.promise;
}

return {
get:_get
};
});


So when I'm using it I am doing
api.URL()
and it's not working.

It used to be hard coded URL so to call it used to be
api.URL
. I really don't want to go through the whole app and convert everything to
api.URL().then(...)
. That would suck.

So how can I nail down this value as a "property" instead of an asynchronous promise that has to be called over and over?

Call it once, fine. Get the value. Put it somewhere. Use the value. Don't ever call the
$http
again after that.

EDIT

This is turning up to be one of the most successful questions I've ever asked, and I am gratefully going through each answer in turn. Thank each one of you.

Answer

Adding a bit to what @ThinkingMedia was saying in the comment, with ui-router when defining controllers you can add a resolve parameter.

In it you can specify some promises that have to resolve before the controller is instantiated, thus you are always sure that the config object is available to the controller or other services that the controller is using.

You can also have parent/child controllers in ui-router so you could have a RootController that resolves the config object and all other controllers inheriting from RootController

.state('root', {
    abstract: true,
    template: '<ui-view></ui-view>',
    controller: 'RootController',
    resolve:{
      config: ['api', function(api){
        return api.initialize();
      }       
    }
  });

and your api factory:

angular.module('myApp').factory('api',
  ["$http", "$q",
  function ($http, $q) {
    var _configObject = null;

    function initialize() {
      return $http.get('/scripts/constants/config.json')
      .then(function (data) {
          _configObject = data;
          return data;
      });
    }

    // you can call this in other services to get the config object. No need to initialize again
    function getConfig() {
      return _configObject;
    }

    return {
      initialize: initialize,
      getConfig: getConfig
    }
  }
  ]
);