AngularJs - field in UI not reflecting updated model value

Here Plunkr

Typical scenario, I have a bunch of elements displayed in ng-repeat. For each line displayed, I have a button that initiates a process (file upload) and a status field. I would like my UI to reflect when the process state changes. This should be easy in Angular with two-way binding, right?

I created a second (child) controller on ng-repeat so that I can just update the status of an element in its own scope and not deal with a set of elements, especially since this process is asynchronous and the user will probably download many files at the same time.

Problem: My understanding of $ scope in Ang / JS is missing - lol. However, despite this, the associated {{xxx}} value in the user interface is not updated when the scoped model value is updated. Press any of the buttons and watch the warnings. How can I customize the UI correctly?

FYI - actually this button calls an API in an external library to download the file and returns me a URL to check the status of my download. I then loop through the url in a setInterval () loop to ping for status until completion or error. I simplified this part in Plunkr because the complexity itself is not an issue. 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);
  };

});

      

+3


source to share


3 answers


You will need to use $ timeout instead setTimeout

to call the digest loop from angular that updates the modal or you have to call the digest loop yourself (wrapping code scope.$apply()

, scope.evalAsync

etc.). The reason is that angular has no idea when it setTimeout

is executed, as it does not happen in angular. You should try not to manually activate the digest loop as much as possible if you have the angular way and can use it. In this case, you can replace setTimeout

with $timeout

, and the model changes will be automatically reflected in the view, as angular calls the digest loop when executed $timeout

. Another advantage is that when using$timeout

it also returns a promise that you can bind and still have a pattern of promises implemented as opposed to 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

When debugging asynchronous operations, I would recommend using console.log (or $log

) instead of warning for debugging.

+5


source


When you change scope variables outside angular like you are in a timeout callback, you can tell angular to update the digest loop using $ 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);
};

      

+1


source


You need to use $timeout

.

setTimeout

removes the value from the corners. Although you can use scope.$apply()

to update the area manually. Better to use angular pre-packaging.

$ time reference

Updated 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);
  };
});

      

0


source







All Articles