AngularJS 与 digest/apply 的异步性质

Async nature of AngularJS with digest/apply

我经常遇到一个问题,我在父控制器中设置了一个范围变量,而子控制器调用这个范围变量。但是,它在函数能够设置范围元素之前调用它,导致它 return 未定义。示例:

父控制器:

module.controller('parent', '$scope', '$http', function ($scope, $http) {
$scope.init = function(profileID, profileViewStatus) {
    //Initiiaze user properities
    $http.get(requestUserInformationGetURL + profileID)
        .success(function(profile) {
            $scope.profile = profile;
            $scope.userID = profile.user_id;
            $scope.username = profile.username; 
            console.log($scope.userID);
        })
        .error(function() {
            exit();
        });
}

子控制器:

module.controller('child', function($scope, $http, fetchInfo) {

console.log($scope.userID);

//Fetch the HTTP POST data for the user profile
var promise = $http({
    method: "post",
    url: fetchInfo,
    data: {
        user_id: $scope.userID //From the parent controller
    },
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
promise.then(function(successResponse) {
    //Populate the scope, log the data
    console.log(successResponse);
    $scope.data = successResponse.data;
}, function(error) {
    alert(error);
});

HTML:

<div ng-controller="parent" init="init('<?php $user_id;?>')">
     <div ng-controller="child">
     </div>
 </div>

经常发生的情况是,userID 在子控制器中会被报告为未定义,但紧接着,它会在父控制器中被报告为已定义。显然,使用 $scope.userID 的子控制器在父控制器中的 init 函数完成之前被调用。如何强制 AngularJS 在子控制器中等待,直到 init 函数完成?我试过类似的东西:

if (!$scope.userID) {
   $scope.$digest();
}

但它没有用,我认为这不是正确的语法。我想,我不完全理解 AngularJS 的 Asycn 性质,而且这种情况发生了多次。你如何控制DOM加载元素来解决这样的问题?

在这种情况下,正确的方法是使用专用服务来处理异步操作、请求、数据缓存等。但是由于您还没有服务层,我将提出使用控制器作用域的简单的基于 Promise 的解决方案承诺对象。

检查您修改的代码:

module.controller('parent', ['$scope', '$http', function ($scope, $http) {
    $scope.init = function (profileID, profileViewStatus) {
        $scope.profilePromise = $http.get(requestUserInformationGetURL + profileID).success(function (profile) {
            $scope.profile = profile;
            $scope.userID = profile.user_id;
            $scope.username = profile.username;
        })
        .error(exit);
    }
}]);

module.controller('child', function($scope, $http, fetchInfo) {

    // Fetch the HTTP POST data for the user profile
    $scope.profilePromise.then(function() {
        return $http({
            method: "post",
            url: fetchInfo,
            data: { user_id: $scope.userID },
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });
    })
    .then(function(successResponse) {
        console.log(successResponse);
        $scope.data = successResponse.data;
    }, function(error) {
        alert(error);
    });
});

如您所见,父控制器 init 方法仍然被调用,但现在它立即设置范围 属性 profilePromise,可在子控制器中访问。

子控制器使用父控制器 profilePromise 对象的 then 方法,它保证 $http 使用 $scope.userID 的请求将在配置文件可用后触发。

通常,您会使用 UI 路由器的路由解析来确保在构造任一控制器之前完成工作。子状态自动访问其父状态的决议。

 //Router configuration
.state('app.inspections.list', {
    url: '',
    templateUrl: 'Template/parent',
    controller: "Parent as vm",
    resolve: {
        profile: ['$http', function ($http) {
            return $http.get(requestUserInformationGetURL + profileID)
                .success(function(profile) {                    
                    console.log(profile.userID);
                    return profile;
                })
                .error(function() {
                    exit();
                });
        }]
    }
}).state('parent.child', {
    url: 'child',
    templateUrl: 'Template/child',
    controller: "Child as vm"
})

   //parent controller
    module.controller('parent', '$scope', 'profile', function ($scope, profile){
        $scope.profile = profile;
        $scope.userID = profile.user_id;
        $scope.username = profile.username; 
    }

//child controller
module.controller('child', 'profile', function($scope, $http, fetchInfo, profile){

console.log(profile.userID);

//Fetch the HTTP POST data for the user profile
var promise = $http({
    method: "post",
    url: fetchInfo,
    data: {
        user_id: profile.userID //From the parent controller
    },
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
promise.then(function(successResponse) {
    //Populate the scope, log the data
    console.log(successResponse);
    $scope.data = successResponse.data;
}, function(error) {
    alert(error);
});

您可以使用 promise ($q service) :尝试使用此代码:

父控制器:

$scope.init = function(profileID, profileViewStatus) {
    var deferred = $q.defer();
    $http.get(requestUserInformationGetURL + profileID)
        .success(function(profile) {
            $scope.profile = profile;
            $scope.userID = profile.user_id;
            $scope.username = profile.username; 
            deferred.resolve($scope.userID);
            console.log($scope.userID);
        })
        .error(function() {
            deferred.reject('error');
            exit();
        });
       return deferred.promise;
}

不要在父控制器中调用init方法

在子控制器中:

$scope.init().then(function(userID){
    var promise = $http({
    method: "post",
    url: fetchInfo,
    data: {
        user_id: userID //From the parent controller
    },
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
   });
   promise.then(function(successResponse) {
    //Populate the scope, log the data
    console.log(successResponse);
    $scope.data = successResponse.data;
   }, function(error) {
    alert(error);
  });
})
.catch(function(error){
 console.log('error');
})

您的问题可能是 $.get 被异步调用,这是默认行为。您的 init 方法实际上可能会按照您期望的顺序调用,但实际情况是:

  • Parent init 被称为
  • $.get 被调用,但服务器的响应是 non-instantaneous
  • Child init 被称为
  • GET 数据从服务器弹回
  • 调用
  • $.get(..).success(function(data){...});处理数据

我会建议其他人使用 promises 来推迟执行。