AngularJs - UI中的字段不反映更新的模型值

AngularJs - field in UI not reflecting updated model value

本文关键字:更新 模型 字段 UI AngularJs      更新时间:2023-09-26

这是Plunkr

一个常见的场景,我有一个在ng-repeat中显示的项集合。对于显示的每一行,我都有一个启动流程(文件上传)的按钮和一个状态字段。我希望我的UI能够反映流程状态的变化。这在Angular的双向绑定中应该很容易,对吧?

我在ng-repeat上创建了第二个(子)控制器,这样我就可以简单地在它自己的范围内更新项的状态,而不是处理项的集合,特别是因为这个过程是异步的,用户可能会并发地上传许多文件。

问题:我对Ang/JS中的$scope的理解是缺乏的-哈哈。但是,当范围模型值更新时,UI中的绑定{{xxx}}值不会更新。单击任何一个按钮并查看警报。如何让UI正确更新?

供参考-在现实中,按钮调用外部库的API来上传文件,并返回给我一个url来检查我上传的状态。然后在setInterval()循环中轮询url以ping状态,直到完成或出现错误。我已经简化了普朗克的这一部分,因为这种复杂性本身不是问题所在。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);
  };
});

你需要使用$timeout而不是setTimeout来从angular中调用更新模态的摘要周期,或者你必须自己调用摘要周期(通过包装代码scope.$apply(), scope.evalAsync等),原因是angular不知道setTimeout何时完成,因为它没有发生在angular中。当你有一种有角度的方式来做事情,并且你可以自由地使用它时,你应该尽量不要手动调用摘要循环。在这种情况下,你可以用$timeout替换setTimeout,模型的变化将自动反映在视图中,因为当$timeout完成时,angular会调用一个摘要周期。另一个优点是,当使用$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进行调试。

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

您需要使用$timeout

setTimeout将该值从angularjs的作用域中移除。而您可以使用scope.$apply()手动更新范围。最好使用angular预构建的包装器。

参考

美元超时更新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);
  };
});