I have an AngularJS service that I want to initialize with some asynchronous data. Something like this:
myModule.service(\'MyService\', function($http) {
You can use JSONP
to asynchronously load service data.
The JSONP request will be made during the initial page load and the results will be available before your application starts. This way you won't have to bloat your routing with redundant resolves.
You html would look like this:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
function MyService {
this.getData = function(){
return MyService.data;
}
}
MyService.setData = function(data) {
MyService.data = data;
}
angular.module('main')
.service('MyService', MyService)
</script>
<script src="/some_data.php?jsonp=MyService.setData"></script>
I used a similar approach to the one described by @XMLilley but wanted to have the ability to use AngularJS services like $http
to load the configuration and do further initialization without the use of low level APIs or jQuery.
Using resolve
on routes was also not an option because I needed the values to be available as constants when my app is started, even in module.config()
blocks.
I created a small AngularJS app that loads the config, sets them as constants on the actual app and bootstraps it.
// define the module of your app
angular.module('MyApp', []);
// define the module of the bootstrap app
var bootstrapModule = angular.module('bootstrapModule', []);
// the bootstrapper service loads the config and bootstraps the specified app
bootstrapModule.factory('bootstrapper', function ($http, $log, $q) {
return {
bootstrap: function (appName) {
var deferred = $q.defer();
$http.get('/some/url')
.success(function (config) {
// set all returned values as constants on the app...
var myApp = angular.module(appName);
angular.forEach(config, function(value, key){
myApp.constant(key, value);
});
// ...and bootstrap the actual app.
angular.bootstrap(document, [appName]);
deferred.resolve();
})
.error(function () {
$log.warn('Could not initialize application, configuration could not be loaded.');
deferred.reject();
});
return deferred.promise;
}
};
});
// create a div which is used as the root of the bootstrap app
var appContainer = document.createElement('div');
// in run() function you can now use the bootstrapper service and shutdown the bootstrapping app after initialization of your actual app
bootstrapModule.run(function (bootstrapper) {
bootstrapper.bootstrap('MyApp').then(function () {
// removing the container will destroy the bootstrap app
appContainer.remove();
});
});
// make sure the DOM is fully loaded before bootstrapping.
angular.element(document).ready(function() {
angular.bootstrap(appContainer, ['bootstrapModule']);
});
See it in action (using $timeout
instead of $http
) here: http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=preview
UPDATE
I would recommend to use the approach described below by Martin Atkins and JBCP.
UPDATE 2
Because I needed it in multiple projects, I just released a bower module that takes care of this: https://github.com/philippd/angular-deferred-bootstrap
Example that loads data from the back-end and sets a constant called APP_CONFIG on the AngularJS module:
deferredBootstrapper.bootstrap({
element: document.body,
module: 'MyApp',
resolve: {
APP_CONFIG: function ($http) {
return $http.get('/api/demo-config');
}
}
});
Have you had a look at $routeProvider.when('/path',{ resolve:{...}? It can make the promise approach a bit cleaner:
Expose a promise in your service:
app.service('MyService', function($http) {
var myData = null;
var promise = $http.get('data.json').success(function (data) {
myData = data;
});
return {
promise:promise,
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData;//.getSomeData();
}
};
});
Add resolve
to your route config:
app.config(function($routeProvider){
$routeProvider
.when('/',{controller:'MainCtrl',
template:'<div>From MyService:<pre>{{data | json}}</pre></div>',
resolve:{
'MyServiceData':function(MyService){
// MyServiceData will also be injectable in your controller, if you don't want this you could create a new promise with the $q service
return MyService.promise;
}
}})
}):
Your controller won't get instantiated before all dependencies are resolved:
app.controller('MainCtrl', function($scope,MyService) {
console.log('Promise is now resolved: '+MyService.doStuff().data)
$scope.data = MyService.doStuff();
});
I've made an example at plnkr: http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview
Easiest way to fetch any initialize use ng-init directory.
Just put ng-init div scope where you want to fetch init data
index.html
<div class="frame" ng-init="init()">
<div class="bit-1">
<div class="field p-r">
<label ng-show="regi_step2.address" class="show-hide c-t-1 ng-hide" style="">Country</label>
<select class="form-control w-100" ng-model="country" name="country" id="country" ng-options="item.name for item in countries" ng-change="stateChanged()" >
</select>
<textarea class="form-control w-100" ng-model="regi_step2.address" placeholder="Address" name="address" id="address" ng-required="true" style=""></textarea>
</div>
</div>
</div>
index.js
$scope.init=function(){
$http({method:'GET',url:'/countries/countries.json'}).success(function(data){
alert();
$scope.countries = data;
});
};
NOTE: you can use this methodology if you do not have same code more then one place.
Also, you can use the following techniques to provision your service globally, before actual controllers are executed: https://stackoverflow.com/a/27050497/1056679. Just resolve your data globally and then pass it to your service in run
block for example.
Based on Martin Atkins' solution, here is a complete, concise pure-Angular solution:
(function() {
var initInjector = angular.injector(['ng']);
var $http = initInjector.get('$http');
$http.get('/config.json').then(
function (response) {
angular.module('config', []).constant('CONFIG', response.data);
angular.element(document).ready(function() {
angular.bootstrap(document, ['myApp']);
});
}
);
})();
This solution uses a self-executing anonymous function to get the $http service, request the config, and inject it into a constant called CONFIG when it becomes available.
Once completely, we wait until the document is ready and then bootstrap the Angular app.
This is a slight enhancement over Martin's solution, which deferred fetching the config until after the document is ready. As far as I know, there is no reason to delay the $http call for that.
Unit Testing
Note: I have discovered this solution does not work well when unit-testing when the code is included in your app.js
file. The reason for this is that the above code runs immediately when the JS file is loaded. This means the test framework (Jasmine in my case) doesn't have a chance to provide a mock implementation of $http
.
My solution, which I'm not completely satisfied with, was to move this code to our index.html
file, so the Grunt/Karma/Jasmine unit test infrastructure does not see it.