ChatGPT解决这个技术问题 Extra ChatGPT

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

我有一个 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();
        }
    };
});

显然这是行不通的,因为如果在 myData 返回之前尝试调用 doStuff(),我将得到一个空指针异常。据我从阅读 herehere 提出的其他一些问题可以看出,我有一些选择,但它们看起来都不是很干净(也许我遗漏了一些东西):

使用“运行”设置服务

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

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() 返回一个承诺,并且所有代码都必须让我们then与承诺进行交互。我宁愿等到 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
    });
});

全局 Javascript Var 我可以将我的 JSON 直接发送到全局 Javascript 变量:

HTML:

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

数据.js:

var dataForMyService = { 
// myData here
};

然后在初始化 MyService 时可用:

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

这也可以,但是我有一个全局 javascript 变量,它闻起来很糟糕。

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

angular - bootstrap asynchronously 遍历代码以使用 $http 从服务器提取数据,然后将数据保存在服务中,然后引导应用程序。

V
Veikko Karsikko

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

在您的服务中公开承诺:

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


非常感谢你的回复!如果我在使用 MyService 的解析映射中还没有服务,它会为我工作。我用我的情况更新了你的 plunker:plnkr.co/edit/465Cupaf5mtxljCl5NuF?p=preview。有什么办法让 MyOtherService 等待 MyService 初始化?
我想我会在 MyOtherService 中链接承诺 - 我已经用链接和一些评论更新了 plunker - 这看起来如何? plnkr.co/edit/Z7dWVNA9P44Q72sLiPjW?p=preview
我试过了,但仍然遇到了一些问题,因为我有指令和其他控制器(我与 $routeProvider 一起使用的控制器正在处理主要的、次要的导航内容......即“MyOtherService”)需要等到“MyService” ' 已解决。我将继续尝试并在我取得任何成功的情况下更新它。我只是希望在初始化我的控制器和指令之前有一个角度挂钩,我可以在其中等待数据返回。再次感谢你的帮助。如果我有一个包含所有内容的主控制器,这将起作用。
这里有一个问题 - 如何将 resolve 属性分配给 $routeProvider 中未提及的控制器。例如,<div ng-controller="IndexCtrl"></div>。在这里,明确提到了控制器,而不是通过路由加载。在这种情况下,如何延迟控制器的实例化呢?
嗯,如果您不使用路由怎么办?这几乎就像说除非您使用路由,否则您不能使用异步数据编写 Angular 应用程序。将数据导入应用程序的推荐方法是异步加载,但是一旦您拥有多个控制器并投入服务,BOOM 就不可能了。
J
JBCP

基于 Martin Atkins 的解决方案,这里有一个完整、简洁的纯 Angular 解决方案:

(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 单元测试基础架构看不到它。


诸如“不要污染全局范围”之类的规则应该只在它们使我们的代码更好(更简单、更可维护、更安全等)的范围内被遵循。我看不出这个解决方案比简单地将数据加载到单个全局变量中更好。我错过了什么?
它允许您使用 Angular 的依赖注入系统来访问需要它的模块中的“CONFIG”常量,但您不会冒破坏其他不需要它的模块的风险。例如,如果您使用全局“配置”变量,则其他第 3 方代码也可能正在寻找相同的变量。
我是一个有角度的新手,这里有一些关于我如何在我的应用程序中解决配置模块依赖项的说明:gist.github.com/dsulli99/0be3e80db9b21ce7b989 ref: tutorials.jenkov.com/angularjs/… 感谢您提供此解决方案。
在下面的其他手动引导解决方案之一的评论中提到了它,但作为一个没有发现它的角度新手,我可以指出你需要在你的 html 代码中删除你的 ng-app 指令才能正常工作- 它正在用这种手动方法替换自动引导程序(通过 ng-app)。如果您不取出 ng-app,该应用程序实际上可能会工作,但您会在控制台中看到各种未知提供程序错误。
p
philippd

我使用了与@XMLilley 描述的方法类似的方法,但希望能够使用像 $http 这样的 AngularJS 服务来加载配置并进行进一步的初始化,而无需使用低级 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');
    }
  }
});

deferredBootstrapper 是要走的路
M
Martin Atkins

“手动引导”案例可以通过在引导之前手动创建注入器来访问 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 指令来防止未解析的模板在工作期间出现。您还可以在 DOM 中提供某种加载指示,方法是提供一些仅在 AngularJS 初始化之前才显示的 HTML:

<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 上创建了这种方法的 a complete, working example,以从静态 JSON 文件加载配置为例。


我认为您不需要将 $http.get() 推迟到文档准备好之后。
@JBCP是的,你是对的,如果你交换事件,它也能正常工作,这样我们就不会等待文档准备好,直到返回 HTTP 响应之后,其优势是可能能够开始 HTTP请求更快。只有引导调用需要等到 DOM 准备好。
我用你的方法创建了一个凉亭模块:github.com/philippd/angular-deferred-bootstrap
@MartinAtkins,我刚刚发现您的好方法不适用于 Angular v1.1+。看起来早期版本的 Angular 只是在应用程序被引导之前不理解“then”。要在您的 Plunk 中查看它,请将 Angular URL 替换为 code.angularjs.org/1.1.5/angular.min.js
C
Community

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

使用手动引导和角度常数。一个简单的 XHR 会为您获取数据,并在其回调中引导 Angular,它处理您的异步问题。在下面的示例中,您甚至不需要创建全局变量。返回的数据仅作为可注入对象存在于角度范围内,甚至不存在于控制器、服务等内部,除非您将其注入。 (就像您将 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 对象会消除 $http 或 JQuery 会为您处理的许多细节,但这个示例没有特殊的依赖关系,至少对于一个简单的 get 是这样。如果您想为您的请求提供更多功能,请加载一个外部库来帮助您。但我认为在这种情况下无法访问 angular 的 $http 或其他工具。

(所以related post


d
dewd

您可以在应用程序的 .config 中为路由创建解析对象,并在函数中传入 $q(promise 对象)和您所依赖的服务的名称,并在服务中 $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 属性,并且路由解析对象中的承诺已被解析,我们的路由控制器开始运行,我们可以将服务中的数据分配给我们的控制器对象。

控制器

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

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


当我有更多时间时,我会试一试。这看起来类似于其他人在 ngModules 中尝试做的事情。
我喜欢这种方法,并且我以前使用过它,但是当我有几条路线时,我目前正试图以一种干净的方式来解决这个问题,每条路线都可能依赖于或不依赖于预取的数据。对此有什么想法吗?
顺便说一句,我倾向于让每个需要预取数据的服务在初始化时发出请求并返回一个承诺,然后使用不同路由所需的服务设置解析对象。我只是希望有一种不那么冗长的方式。
@dewd这就是我的目标,但如果有某种方式可以说“首先获取所有这些东西,无论加载哪条路线”而不必重复我的解析块,我会更喜欢。他们都有自己所依赖的东西。但这不是什么大不了的事,只是感觉更干了一点:)
这是我最终采取的路线,除了我必须使 resolve 成为一个对象,其属性是函数。所以它最终成为resolve:{ dataFetch: function(){ // call function here } }
t
testing123

所以我找到了解决方案。我创建了一个 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...
}

这对我很有用,但如果有人有任何反馈,我愿意接受任何反馈。 }


我喜欢你的模块化方法。我发现 $routeScope 可用于请求数据的服务,您可以在 $http.success 回调中为其分配数据。但是,将 $routeScope 用于非全局项目会产生异味,并且数据实际上应该分配给控制器 $scope。不幸的是,我认为您的方法虽然具有创新性,但并不理想(尽管尊重找到适合您的东西)。我只是确定必须有一个仅限客户端的答案,它以某种方式等待数据并允许分配范围。搜索继续!
如果它对某人有用,我最近看到了一些不同的方法来查看其他人编写并添加到 ngModules 网站的模块。当我有更多时间时,我将不得不开始使用其中一个,或者弄清楚他们做了什么并将其添加到我的东西中。
C
Community

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


h
hegemon

您可以使用 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>

R
Ronak Amlani

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

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

索引.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;
    });
  };

注意:如果您没有多个相同的代码,则可以使用此方法。


不建议根据文档使用 ngInit:docs.angularjs.org/api/ng/directive/ngInit