角度异步调用

时间:2014-08-09 13:36:09

标签: javascript angularjs angularjs-scope

我的Angular应用程序中发生了一个奇怪的问题,我认为Angular的异步性是原因,但我不确定如何解决它。

.run我有以下内容:

app.run(function ($user, $soundcloud) {
    $user.set();
    $soundcloud.set();
});

然后是两个服务......

app.service('$user', ['$http',  function ($http) {    
    var currentUser;    
    this.set = function () {
        $http.get('/users/active/profile')
            .success(function (obj) {
                currentUser = obj;
            })
            .error(function () {
                console.log('ERR');
            })
    }    
    this.fetch = function () {
        return currentUser;
    }

}]);    
app.service('$soundcloud', ['$http', function ($http) {    
    var sc;    
    this.set = function () {
        $http.get('/users/active/services/soundcloud')
            .success(function (obj) {
                sc = obj;
            })
            .error(function () {
                console.log('Error fetching soundcloud feed')
            })
    }    
    this.fetch = function () {
        return sc;
    }    
}]);

我遇到的问题是,当我转到/ profile并重新加载页面时,$ soundcloud.fetch()返回的数据不可用,除非我将$ soundcloud.fetch()包含在{{1显然不理想的功能,所以我想知道我应该在setTimeout()放置哪些内容,以便在页面加载时立即提供数据。

这是我的控制器目前:

$soundcloud.set()

如果我在setTimeout之外取app.controller('profileController', ['$scope', '$user', '$soundcloud', function ($scope, $user, $soundcloud) { $scope.activeUser = $user.fetch(); $scope.activeSoundcloud = $soundcloud.fetch(); console.log($user.fetch()); setTimeout(function () { console.log($soundcloud.fetch()); },100); }]); ,那么我得到$soundcloud.fetch()作为结果。应该在哪里调用$ soundcloud.set / fetch以使两组数据(用户和声音云)立即可用?

3 个答案:

答案 0 :(得分:1)

<强> PLUNKER DEMO

解决此问题的一种方法是获取所有必需的数据,然后手动引导应用程序。

为此,请创建bootstrapper.js。您可以访问内置的角度服务,而无需通过angular.injector(['ng']).invoke()引导模块,该模块接受具有可注入的依赖项的函数。 urlMap变量只是设置的键值存储及其各自的URL。您通过urlMap循环此angular.forEach(),然后逐个获取所有设置,并将每个设置与每个相应的键一起存储在settings变量中。将每个$http承诺请求存储在promises数组中,以$q.all()解析它。当所有承诺都已解决后,调用bootstrap()函数,在Settings模块中添加app.settings值,然后手动引导应用程序。

<强> bootstrapper.js

angular.injector(['ng']).invoke(function($http, $q) {

  var urlMap = {
    $user: '/users/active/profile',
    $soundcloud: '/users/active/services/soundcloud'
  };

  var settings = {};

  var promises = [];

  var appConfig = angular.module('app.settings', []);

  angular.forEach(urlMap, function(url, key) {
    promises.push($http.get(url).success(function(data) {
      settings[key] = data;
    }));
  });

  $q.all(promises).then(function() {
    bootstrap(settings);
  }).catch(function() {
    bootstrap();
  });

  function bootstrap(settings) {
    appConfig.value('Settings', settings);

    angular.element(document).ready(function() {
      angular.bootstrap(document, ['app', 'app.settings']);
    });
  }

});

<强> app.js

angular.module('app', [])

  .run(function(Settings) {
    if(Settings === null) {
      // go to login page or
      // a page that will display an
      // error if any of the requested
      // settings failed
    }
  })

  .controller('ProfileController', function($scope, Settings) {
    console.log(Settings);
  });

答案 1 :(得分:0)

$ soundcloud.set()的结果会覆盖sc的值。

如果在返回$ soundcloud.set()之前尝试将$ soundcloud.fetch中的值绑定到作用域,则会将undefined绑定到作用域,因为var sc尚未定义。当$ soundcloud.set()设置sc字段时,范围变量将永远不会得到通知,因为它只获得未定义的值并且对sc一无所知。

最简单的解决方法是创建一个永远不会被删除的包装器对象。这样的事情。

app.service('$soundcloud', ['$http', function ($http) {    
var sc = {};    // define an object instead of undefined
this.set = function () {
    $http.get('/users/active/services/soundcloud')
        .success(function (obj) {
            sc.soundcloud = obj; // write to a field on the object, 
                                 // do not override sc, since sc is already returned
        })
        .error(function () {
            console.log('Error fetching soundcloud feed')
        })
}    
this.fetch = function () {
    return sc;
}    
}]);

当您使用$ soundcloud.fetch()的结果时,您需要参考soundcloud字段。至少在调用$ soundcloud.set时会始终更新

答案 2 :(得分:0)

你对异步性的问题是正确的。

  1. .run()中的代码会触发异步.get()
  2. 在结果返回之前,并且运行了传递给.success()的回调,您运行fetch()。因此,fetch()会返回未定义的sc
  3. 然后你console.log()这个,因此你看到未定义。
  4. 使用.get()中的数据解析承诺,并将sc分配给结果。
  5. @Andre Paap 刚刚概述的方法很好 - 主要是通过创建一个空白的引用对象并在回调中更新其中一个属性(而不是完全覆盖引用),你可以仍然将它分配给范围,并且角度将在填充后(使用数据绑定)“神奇地”更新视图。 然而,如果您立即console.log()结果,您仍会看到同样的事情。

    换句话说,如果你在服务中有这个:

    var currentUser = {};    
    this.set = function () {
        return $http.get('/users/active/profile')
            .success(function (obj) {
                currentUser.name = obj.name;
            })
            .error(function () {
                console.log('ERR');
            })
    }    
    

    这在您的模板中:

    <span ng-show="currentUser.name"> Hello {{currentUser.name}}! </span>
    

    然后将其放入您的控制器会导致上述范围在set()调用返回后正确显示:

    $scope.currentUser =  $user.fetch()
    // NOTE: the below would still log undefined:
    console.log($scope.currentUser.name) //<- undefined.
    

    如果您只想在返回.set()调用后运行某些代码,那么您可以利用$http方法返回promise的事实,如下所示:

    $user.set().then(function(){
      // This code runs *after* the call has returned.
      $scope.currentUser = $user.fetch()
      console.log($scope.currentUser.name) //<- "Ed"
    });