4

这是Plunkr

一个常见的场景是,我在 ng-repeat 中显示了一组项目。对于显示的每一行,我都有一个启动流程(文件上传)的按钮和一个状态字段。我希望我的 UI 能够在流程状态发生变化时反映出来。这在 Angular 中使用 2 路绑定应该很容易,对吧?

我在 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);
  };

});
4

3 回答 3

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)而不是 alert 进行调试。

于 2014-08-10T23:56:30.577 回答
1

当您像在超时回调中一样更改 Angular 之外的范围变量时,您可以告诉 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);
};
于 2014-08-10T23:34:50.487 回答
0

你需要使用$timeout.

setTimeout从 angularjs 范围中删除值。虽然您可以使用scope.$apply()手动更新范围。最好使用有角度的预构建包装器。

$超时参考

更新了 Plunkr

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);
  };
});
于 2014-08-10T23:58:47.717 回答