AngularJS : Initialize service with asynchronous data

后端 未结 10 1736
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-22 03:09

I have an AngularJS service that I want to initialize with some asynchronous data. Something like this:

myModule.service(\'MyService\', function($http) {
            


        
相关标签:
10条回答
  • 2020-11-22 03:18

    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>
    
    0 讨论(0)
  • 2020-11-22 03:20

    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');
        }
      }
    });
    
    0 讨论(0)
  • 2020-11-22 03:24

    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

    0 讨论(0)
  • 2020-11-22 03:24

    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.

    0 讨论(0)
  • 2020-11-22 03:27

    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.

    0 讨论(0)
  • 2020-11-22 03:28

    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.

    0 讨论(0)
提交回复
热议问题