一个常见的场景,我有一个以ng-repeat显示的项目集合。对于显示的每一行,我都有一个启动进程(文件上载)的按钮和一个状态字段。我希望我的UI能够在过程状态发生变化时进行反映。这在Angular w /双向绑定中应该很容易,对吗?
我在ng-repeat上创建了第二个(子)控制器,这样我就可以简单地更新它自己范围内的项目的状态,而不是处理项目集合,特别是因为这个过程是异步的,用户将可能会同时上传多个文件。
问题:我对Ang / JS中的$ scope的理解缺乏 - 大声笑。但是,严重的是,当更新范围模型值时,UI中的绑定{{xxx}}值不会更新。单击任一按钮并观察警报。如何让UI正确更新?
仅供参考 - 实际上该按钮调用外部库上的API来上传文件并返回给我一个URL以检查我上传的状态。然后我在setInterval()循环中轮询url以ping状态直到完成或错误。我已经简化了Plunkr中的那个部分,因为这种复杂性本身不是问题。 Plunkr
<!DOCTYPE html>
<html ng-app="myapp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.7/angular.js" data-semver="1.2.7"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<table>
<th></th>
<th>Id</th>
<th>Name</th>
<th>Status</th>
<tr ng-repeat="item in items" ng-controller="ChildCtrl">
<td><button ng-click="updateStatus(item)">click</button></td>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.status}}</td>
</tr>
</table>
</body>
</html>
JS
var app = angular.module('myapp', []);
app.controller('MainCtrl', function($scope) {
$scope.items = [ {id: 1, name: "Moe", status: "init"}
, {id: 3, name: "Larry", status: "init"}
, {id: 2, name: "Curly", status: "init"}
];
});
app.controller('ChildCtrl', function($scope) {
$scope.updateStatus = function(item){
$scope.myItem = item;
alert('child: ' + item.id + ' status: ' + item.status);
item.status = 'clicked';
alert('status just update in UI to: ' + item.status);
callResult = fakeAjaxCall($scope);
alert('callResult: ' + callResult);
};
var fakeAjaxCall = function(scope){
setTimeout(function (item) {
if (-1 == -1) { //success
result = "Wow, it worked!";
alert('current status: ' + scope.myItem.status);
alert('ajax result: ' + result);
scope.myItem.status = result;
alert('new status: ' + scope.myItem.status);
alert("but the status in the UI didn't update");
}
}, 2000);
};
});
答案 0 :(得分:6)
您需要使用$timeout而不是setTimeout
来从更新模式的角度内调用摘要周期,或者您必须自己调用摘要周期(通过包装代码scope.$apply()
,scope.evalAsync
等......),原因是角度不知道setTimeout
何时完成,因为它不会在角度内发生。当你有一个有角度的方式来做事情并且你可以自由地使用它时,你应该尽量不要手动调用摘要周期。在这种情况下,您可以将setTimeout
替换为$timeout
,并且模型更改将自动反映在视图中,因为角度会在$timeout
完成时调用摘要周期。另一个优点是,当使用$timeout
时,它会返回一个您可以链接的承诺,并且在与setTimeout
相对时仍然实现了承诺模式。
app.controller('ChildCtrl', function($scope, $timeout) {
$scope.updateStatus = function(item){
$scope.myItem = item;
console.log('child: ' + item.id + ' status: ' + item.status);
item.status = 'clicked';
console.log('status now updates in UI to: ' + item.status);
callResult = fakeAjaxCall($scope);
console.log('callResult: ' + callResult);
};
var fakeAjaxCall = function(scope){
$timeout(function (item) {
if (-1 == -1) { //success
result = "Wow, it worked!";
console.log('current status: ' + scope.myItem.status);
console.log('ajax result: ' + result);
scope.myItem.status = result;
console.log('new status: ' + scope.myItem.status);
console.log("but the status in the UI doesn't update");
}
}, 2000);
};
<强> Plnkr 强>
在调试异步操作时,我建议使用console.log(或$log
)而不是调试警报。
答案 1 :(得分:1)
当您在超出范围内更改范围变量时,您可以告诉angular使用$ scope更新摘要周期。$ apply。
var fakeAjaxCall = function(scope){
setTimeout(function (item) {
if (-1 == -1) { //success
result = "Wow, it worked!";
alert('current status: ' + scope.myItem.status);
alert('ajax result: ' + result);
scope.$apply(scope.myItem.status = result); // <---- Changed
alert('new status: ' + scope.myItem.status);
alert("but the status in the UI doesn't update");
}
}, 2000);
};
答案 2 :(得分:0)
您需要使用$timeout
。
setTimeout
从angularjs范围中删除值。虽然您可以使用scope.$apply()
手动更新范围。最好使用角度预制包装。
app.controller('ChildCtrl', function($scope,$timeout) {
$scope.updateStatus = function(item){
$scope.myItem = item;
alert('child: ' + item.id + ' status: ' + item.status);
item.status = 'clicked';
alert('status now updates in UI to: ' + item.status);
callResult = fakeAjaxCall($scope);
alert('callResult: ' + callResult);
};
var fakeAjaxCall = function(scope){
$timeout(function (item) {
if (-1 == -1) { //success
result = "Wow, it worked!";
alert('current status: ' + scope.myItem.status);
alert('ajax result: ' + result);
$scope.myItem.status = result;
alert('new status: ' + scope.myItem.status);
alert("but the status in the UI doesn't update");
}
}, 2000);
};
});