Join the Stack Overflow Community
Stack Overflow is a community of 6.5 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

Plunkr here: https://plnkr.co/edit/aWBE55?p=preview

Let's say I have a view like this:

<body>
  <div ng-app="myApp">
    <div ng-controller="myAppController as app" >
      <input type="text" ng-model="app.colorName" /><button ng-click="app.addColor()">add color</button>
      <div ng-repeat="c in app.colors">{{c.colorName}}</div>
    </div>
  </div>
</body>

and I have a controller like this:

var Color = function(options) {
  var self = this;
  self.colorName = options.colorName;
};

(function(angular) {
  'use strict';
  angular.module('myApp', []).controller('myAppController', function myAppController($filter) {
    var self = this;
    self.colorName = '';
    self.colors = [
      new Color({colorName: 'Red'}),
      new Color({colorName: 'Blue'}), 
      new Color({colorName: 'Green'})
      ];

    self.addColor = function() {
      console.log("added a new color " + self.colorName);
      self.colors.push(new Color({colorName: self.colorName}));
    };

    setTimeout(function() {
      console.log("added new color in another thread");
      self.colors.push(new Color({colorName: 'Purple'}));
        }, 1000);
    });
})(window.angular);

See that little setTimeout guy pushing another color onto the array? It seems that because it's being added from an async call (because of the setTimeout), the view does not refresh immediately and show the Purple color after it's been added.

Interestingly, even clicking into the textbox and then out (which touches that ng-model property) triggers a view update and 'Purple' appears... and, of course, adding a color by pushing the button refreshes the view immediately.

Why does this happen and what should I do about it?

I could do $scope.$apply, but that seems particularly hideous as the real world example (which involves modal dialogs with callbacks) is component-based and trying to get away from doing $scope-y things. This example was worked up for simplicity.

share|improve this question
    
use $timeout() instead of setTimeout(). Angular has no way of knowing when a setTimeout is finished so it cannot update your bindings – rob 23 hours ago
1  
btw the callback function from setTimeout() still runs in the same thread as the rest of your app – rob 23 hours ago
    
timeout's not the issue... that's just an example of a callback. The real world example isn't using setTImeout – Brett Green 23 hours ago
    
if you run something asyncronous that Angular isn't aware of you need to manually trigger a digest. e.g. with $scope.$apply() – rob 23 hours ago
    
@BrettGreen didn't see these comments before posting my answer :(. @rob is right, but keep in mind if a $scope.$apply() is going to be applied is safer to be applied inside a $timeout in order to avoid a possible $digest error – Asiel Leal Celdeiro 23 hours ago

You need to use $timeout instead of setTimeout since the first is the angular wrapper for the second. When using $timeout the code inside it is in the angular world while in the second one it isn't.

var Color = function(options) {
  var self = this;
  self.colorName = options.colorName;
};

(function(angular) {
  'use strict';
  angular.module('myApp', []).controller('myAppController', function myAppController($filter, $timeout) {
    var self = this;
    self.colorName = '';
    self.colors = [
      new Color({
        colorName: 'Red'
      }),
      new Color({
        colorName: 'Blue'
      }),
      new Color({
        colorName: 'Green'
      })
    ];

    self.addColor = function() {
      console.log("added a new color " + self.colorName);
      self.colors.push(new Color({
        colorName: self.colorName
      }));
    };

    $timeout(function() {
      console.log("added new color in another thread");
      self.colors.push(new Color({
        colorName: 'Purple'
      }));
    }, 1000);
  });
})(window.angular);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>


<body>
  <div ng-app="myApp">
    <div ng-controller="myAppController as app">
      <input type="text" ng-model="app.colorName" />
      <button ng-click="app.addColor()">add color</button>
      <div ng-repeat="c in app.colors">{{c.colorName}}</div>
    </div>
  </div>
</body>

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.