AngularJS:使用异步数据初始化服务

2020/09/26 01:21 · javascript ·  · 0评论

我有一个AngularJS服务,我想使用一些异步数据进行初始化。像这样:

myModule.service('MyService', function($http) {
    var myData = null;

    $http.get('data.json').success(function (data) {
        myData = data;
    });

    return {
        setData: function (data) {
            myData = data;
        },
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

显然,这是行不通的,因为如果doStuff()myData返回之前尝试进行调用我将得到一个空指针异常。据我从阅读这里这里提出的其他一些问题中所知道的,我有一些选择,但是它们似乎都不是很干净(也许我错过了一些东西):

安装服务“运行”

设置我的应用时,请执行以下操作:

myApp.run(function ($http, MyService) {
    $http.get('data.json').success(function (data) {
        MyService.setData(data);
    });
});

然后我的服务将如下所示:

myModule.service('MyService', function() {
    var myData = null;
    return {
        setData: function (data) {
            myData = data;
        },
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

这有时会起作用,但是如果异步数据发生的时间比初始化所有东西所花费的时间长,则在调用时会出现空指针异常 doStuff()

使用承诺对象

这可能会起作用。我调用MyService的每个地方的唯一缺点是,我必须知道doStuff()返回了一个promise,所有代码都必须与我们then进行交互才能与promise交互。我宁愿等到myData返回之后再加载我的应用程序。

手动引导

angular.element(document).ready(function() {
    $.getJSON("data.json", function (data) {
       // can't initialize the data here because the service doesn't exist yet
       angular.bootstrap(document);
       // too late to initialize here because something may have already
       // tried to call doStuff() and would have got a null pointer exception
    });
});

全局Java语言Var
我可以将JSON直接发送到全局Java语言变量:

HTML:

<script type="text/javascript" src="data.js"></script>

data.js:

var dataForMyService = { 
// myData here
};

然后在初始化时可用MyService

myModule.service('MyService', function() {
    var myData = dataForMyService;
    return {
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

这也可以,但是我有一个全局的javascript变量,它闻起来不好。

这些是我唯一的选择吗?这些选择之一比其他选择更好吗?我知道这是一个很长的问题,但我想表明我已尝试探索所有选择。任何指导将不胜感激。

你看了$routeProvider.when('/path',{ resolve:{...}吗?它可以使诺言方法更加简洁:

在您的服务中展现承诺:

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();
      }
    };
});

添加resolve到您的路由配置:

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;
      }
    }})
  }):

在解决所有依赖关系之前,不会实例化您的控制器:

app.controller('MainCtrl', function($scope,MyService) {
  console.log('Promise is now resolved: '+MyService.doStuff().data)
  $scope.data = MyService.doStuff();
});

我在plnkr上做了一个例子:http ://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview

基于马丁·阿特金斯(Martin Atkins)的解决方案,这是一个完整,简洁的纯角度解决方案:

(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']);
        });
    }
  );
})();

此解决方案使用自执行的匿名函数来获取$ http服务,请求配置,并将其注入到名为CONFIG的常量(如果可用)中。

完全完成后,我们将等到文档准备就绪后再引导Angular应用程序。

这是对Martin解决方案的略微增强,该解决方案将获取配置推迟到文档准备好之后才进行。据我所知,没有理由为此延迟$ http调用。

单元测试

注意:我发现当app.js文件中包含代码时,此解决方案在单元测试时效果不佳原因是上述代码在加载JS文件后立即运行。这意味着测试框架(在我的例子中为Jasmine)没有机会提供的模拟实现$http

我不完全满意的解决方案是将该代码移到我们的index.html文件中,因此Grunt / Karma / Jasmine单元测试基础结构看不到它。

我使用了与@XMLilley描述的方法类似的方法,但希望能够使用AngularJS服务,例如$http加载配置并进行进一步的初始化,而无需使用低级API或jQuery。

resolve在路由上使用也不是一种选择,因为我需要在启动我的应用程序时将这些值作为常量使用,即使是在module.config()块中也是如此。

我创建了一个小的AngularJS应用程序,该应用程序加载配置,将其设置为实际应用程序上的常量,然后对其进行引导。

// 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']);
});

此处查看实际运行情况(使用$timeout代替$http):http : //plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=preview

更新

我建议使用Martin Atkins和JBCP下面描述的方法。

更新2

因为我在多个项目中需要它,所以我刚刚发布了一个Bower模块来解决这个问题:https : //github.com/philippd/angular-deferred-bootstrap

从后端加载数据并在AngularJS模块上设置名为APP_CONFIG的常量的示例:

deferredBootstrapper.bootstrap({
  element: document.body,
  module: 'MyApp',
  resolve: {
    APP_CONFIG: function ($http) {
      return $http.get('/api/demo-config');
    }
  }
});

通过在引导程序之前手动创建注射器,“手动引导程序”案例可以访问Angular服务。该初始注入器将独立运行(不附加到任何元素上),并且仅包括已加载模块的子集。如果您只需要核心Angular服务,则只需加载即可ng,如下所示:

angular.element(document).ready(
    function() {
        var initInjector = angular.injector(['ng']);
        var $http = initInjector.get('$http');
        $http.get('/config.json').then(
            function (response) {
               var config = response.data;
               // Add additional services/constants/variables to your app,
               // and then finally bootstrap it:
               angular.bootstrap(document, ['myApp']);
            }
        );
    }
);

例如,您可以使用该module.constant机制使数据可用于您的应用程序:

myApp.constant('myAppConfig', data);

myAppConfig现在可以注入就像任何其他服务,尤其是在配置阶段是可用的:

myApp.config(
    function (myAppConfig, someService) {
        someService.config(myAppConfig.someServiceConfig);
    }
);

或者,对于较小的应用程序,您可以将全局配置直接注入到您的服务中,以牺牲在整个应用程序中传播有关配置格式的知识为代价。

当然,由于此处的异步操作将阻止应用程序的引导,从而阻止模板的编译/链接,因此明智的方法是使用ng-cloak指令防止未解析的模板在工作期间显示。您还可以通过提供一些仅在AngularJS初始化之前显示的HTML来在DOM中提供某种加载指示:

<div ng-if="initialLoad">
    <!-- initialLoad never gets set, so this div vanishes as soon as Angular is done compiling -->
    <p>Loading the app.....</p>
</div>
<div ng-cloak>
    <!-- ng-cloak attribute is removed once the app is done bootstrapping -->
    <p>Done loading the app!</p>
</div>

在Plunker上创建此方法的完整且有效的示例,以从静态JSON文件加载配置为例。

我遇到了同样的问题:我喜欢这个resolve对象,但这仅适用于ng-view的内容。如果您有ng-view之外的控制器(例如用于顶级导航),并且在路由甚至开始发生之前需要使用数据进行初始化,该怎么办?我们如何避免仅仅为了使之工作而在服务器端混乱?

使用手动引导程序和角度常数幼稚的XHR可以获取您的数据,并在其回调中引导角度,从而处理您的异步问题。在下面的示例中,您甚至不需要创建全局变量。返回的数据仅在角度范围内作为可注入对象存在,并且甚至不存在于控制器,服务等内部,除非您将其注入。(就像您将resolve对象的输出注入到路由视图的控制器中一样。)如果此后您希望与该数据作为服务进行交互,则可以创建服务,注入数据,没有人会更明智。 。

例:

//First, we have to create the angular module, because all the other JS files are going to load while we're getting data and bootstrapping, and they need to be able to attach to it.
var MyApp = angular.module('MyApp', ['dependency1', 'dependency2']);

// Use angular's version of document.ready() just to make extra-sure DOM is fully 
// loaded before you bootstrap. This is probably optional, given that the async 
// data call will probably take significantly longer than DOM load. YMMV.
// Has the added virtue of keeping your XHR junk out of global scope. 
angular.element(document).ready(function() {

    //first, we create the callback that will fire after the data is down
    function xhrCallback() {
        var myData = this.responseText; // the XHR output

        // here's where we attach a constant containing the API data to our app 
        // module. Don't forget to parse JSON, which `$http` normally does for you.
        MyApp.constant('NavData', JSON.parse(myData));

        // now, perform any other final configuration of your angular module.
        MyApp.config(['$routeProvider', function ($routeProvider) {
            $routeProvider
              .when('/someroute', {configs})
              .otherwise({redirectTo: '/someroute'});
          }]);

        // And last, bootstrap the app. Be sure to remove `ng-app` from your index.html.
        angular.bootstrap(document, ['NYSP']);
    };

    //here, the basic mechanics of the XHR, which you can customize.
    var oReq = new XMLHttpRequest();
    oReq.onload = xhrCallback;
    oReq.open("get", "/api/overview", true); // your specific API URL
    oReq.send();
})

现在,您的NavData常数存在。继续并将其注入到控制器或服务中:

angular.module('MyApp')
    .controller('NavCtrl', ['NavData', function (NavData) {
        $scope.localObject = NavData; //now it's addressable in your templates 
}]);

当然,使用裸XHR对象会剥夺$httpJQuery可以处理的许多细节,但是此示例在没有特殊依赖性的情况下(至少对于simple而言)有效get如果您需要更多功能,请加载外部库以帮助您。但我认为$http在这种情况下无法访问angular 或其他工具。

SO 相关的帖子

您可以在应用程序的.config中为该路由创建resolve对象,并在函数$ q中传递(承诺对象)和您所依赖的服务的名称,然后在服务中$ http的回调函数,如下所示:

路线配置

app.config(function($routeProvider){
    $routeProvider
     .when('/',{
          templateUrl: 'home.html',
          controller: 'homeCtrl',
          resolve:function($q,MyService) {
                //create the defer variable and pass it to our service
                var defer = $q.defer();
                MyService.fetchData(defer);
                //this will only return when the promise
                //has been resolved. MyService is going to
                //do that for us
                return defer.promise;
          }
      })
}

在调用defer.resolve()之前,Angular不会渲染模板或使控制器不可用。我们可以在我们的服务中做到这一点:

服务

app.service('MyService',function($http){
       var MyService = {};
       //our service accepts a promise object which 
       //it will resolve on behalf of the calling function
       MyService.fetchData = function(q) {
             $http({method:'GET',url:'data.php'}).success(function(data){
                 MyService.data = data;
                 //when the following is called it will
                 //release the calling function. in this
                 //case it's the resolve function in our
                 //route config
                 q.resolve();
             }
       }

       return MyService;
});

既然MyService已将数据分配给它的data属性,并且路由解析对象中的promise已经解析,我们的路由控制器就会生效,我们可以将服务中的数据分配给控制器对象。

控制器

  app.controller('homeCtrl',function($scope,MyService){
       $scope.servicedata = MyService.data;
  });

现在,我们在控制器范围内的所有绑定都将能够使用源自MyService的数据。

所以我找到了解决方案。我创建了angularJS服务,我们将其称为MyDataRepository并为其创建了一个模块。然后,我从服务器端控制器提供此javascript文件:

HTML:

<script src="path/myData.js"></script>

服务器端:

@RequestMapping(value="path/myData.js", method=RequestMethod.GET)
public ResponseEntity<String> getMyDataRepositoryJS()
{
    // Populate data that I need into a Map
    Map<String, String> myData = new HashMap<String,String>();
    ...
    // Use Jackson to convert it to JSON
    ObjectMapper mapper = new ObjectMapper();
    String myDataStr = mapper.writeValueAsString(myData);

    // Then create a String that is my javascript file
    String myJS = "'use strict';" +
    "(function() {" +
    "var myDataModule = angular.module('myApp.myData', []);" +
    "myDataModule.service('MyDataRepository', function() {" +
        "var myData = "+myDataStr+";" +
        "return {" +
            "getData: function () {" +
                "return myData;" +
            "}" +
        "}" +
    "});" +
    "})();"

    // Now send it to the client:
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "text/javascript");
    return new ResponseEntity<String>(myJS , responseHeaders, HttpStatus.OK);
}

然后,我可以在需要的地方注入MyDataRepository:

someOtherModule.service('MyOtherService', function(MyDataRepository) {
    var myData = MyDataRepository.getData();
    // Do what you have to do...
}

这对我来说非常有效,但是我愿意接受任何反馈(如果有任何反馈)。}

另外,在执行实际的控制器之前,您可以使用以下技术在全球范围内配置服务:https : //stackoverflow.com/a/27050497/1056679只需全局解析您的数据,然后将其run例如块形式传递给您的服务

您可以JSONP用来异步加载服务数据。JSONP请求将在初始页面加载期间发出,并且结果将在您的应用程序启动之前可用。这样,您就不必使用多余的解析来充实您的路由。

您的html看起来像这样:

<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>

获取任何初始化的最简单方法是使用ng-init目录。

只需将ng-init div范围放在要获取初始化数据的位置

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;
    });
  };

注意:如果您的相同代码不超过一个位置,则可以使用此方法。

本文地址:http://javascript.askforanswer.com/angularjsshiyongyibushujuchushihuafuwu.html
文章标签: ,   ,   ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!