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 来推迟执行。
我经常遇到一个问题,我在父控制器中设置了一个范围变量,而子控制器调用这个范围变量。但是,它在函数能够设置范围元素之前调用它,导致它 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 来推迟执行。