Check out our Angular Book Series.

Why didn't my Angular View update after changing the $Scope variable?

Binding in AngularJS works magically. When an element in the controller changes the view is updated automatically. When an input in the view is updated, the controller value is automatically updated.

This is because every time the user interacts with the app, AngularJS automatically runs the $digest() method which automatically updates all bindings.

I am working on an app for a client that needed to execute JavaScript code when a binding changes. I had one view, and controller, which displayed some data filtering options to the user. A second view, and controller, displayed data loaded from the remote services, based on the filtering options. When the filtering elements were changed, a remote service call was made in the filtering view, and the results needed to modify the data view.

However, I discovered that when the results came back from the service call, the view was not updating with the new data. Why is that?

There are a few things at play here and I'm going to start with some simple examples of binding, then expand to discuss $watch(), and finally I'll talk about how to use $apply() to solve the problem.

Setting up a Simple Application with Binding

To demonstrate watches I'm going to start with a simple application. It will take two inputs from a user; a first name and a last-name. Then it will display the values to the user, updating as the user types.

First, create a simple HTML page. Then import the Angular library:


<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js" ></script>

Then create another script block, with a module and controller:


<script>
var watchTest = angular.module('WatchTest',[]);

watchTest.controller('watchTestCtrl',['$scope', function($scope){
}]);
</script>

Inside the controller, add some scope variables. One for the first name and one for the last name:


$scope.firstName = "Jeffry";
$scope.lastName = "Houser";

I'm also going to add a reset function:


$scope.onReset = function(){
$scope.firstName = "";
$scope.lastName = "";
}

The HTML behind this application will put the ng-app tag on the body, and point the watchTestCtrl on the div tag:


<body ng-app="WatchTest">
<div ng-controller="watchTestCtrl">
First Name: <input type="text" ng-model="firstName" /><br/>
Last Name: <input type="text" ng-model="lastName" /><br/>
Hello: {{firstName}} {{lastName}}<br/>
<Br/>
<button ng-click="onReset()" >Reset</button>
</div>
</body>

See this app in action or play with the Plunker.

Setting up a Simple Watch

AngularJS binding, with ng-model and the double curly bracket syntax, does not allow you to run JavaScript code when a value changes. Thankfully, it does provide a $scope function, $watch(), for this purpose.

Let's set up a simple watch to show how we can run additional code when values change.

Inside the controller, add the watch:


$scope.$watch( 'firstName',
function(newValue, oldValue){
console.log('firstName Changed');
console.log(newValue);
console.log(oldValue);
}
);

$scope.$watch( 'lastName',
function(newValue, oldValue){
console.log('lastName Changed');
console.log(newValue);
console.log(oldValue);
}
);

The $watch() is a function on the $scope service. It accepts two arguments. The first is a string, representing the value that you want to watch for changes. This could also be a function that returns the value you want to watch. The second is a function which is executed whenever the value changes. The arguments for the change function are the newValue and the oldValue.

For the sake of the example, I just logged the new and old values to the console; however you could perform other functionality, such as instigating a remote service call or other processing.

You can try to run this app or play with the Plunker. You'll need to keep the web developer console of your browser open to see the log statements.

The problem with Binding and Asynchronous calls

For binding, or the $watch() method, to execute the variable change must happen within the context of an AngularJS $digest(). Angular automatically runs $digest() whenever user interaction takes places which affects the $scope. However, the $digest() will not occur without user interaction. When retrieving, or changing, data asynchronously, the $digest() will not be triggered, the bindings will not update, and the $watch() method never fires.

To demonstrate the problem, I am going to use the JavaScript setTimeout() function instead building out a full remote service call. First, I'm going to add a JavaScript method to change the firstName with the setTimeout() function:


$scope.triggerChange = function () {
setTimeout(function(){
console.log('First name being reset');
$scope.firstName = '';
}, 1000);
};

I'm also going to add a button to trigger that change:


<button ng-click="triggerChange()" >Trigger Change</button>

So, when you click the "Trigger Change" button in the UI; the triggerChange() method will be executed, queuing up the timeout function to run in 1 second. When it runs, we see a console.log() and the value is changed. Try this and you'll see that the values in the UI are not updated even though you can see the log in the console, proving that the element did execute.

As a helper, you can add a method to log the user information:


$scope.logName = function () {
console.log($scope.firstName);
console.log($scope.lastName);
}

And add a button to trigger this:


<button ng-click="logName()" >Log Name</button>

Click the logName button, and you'll see that the values in the UI automatically get updated. This is because the user interaction forced the $digest() method to execute, which updated the bindings, and also caused the $watch() function to execute.

You can Test the app or play with the Plunker

Forcing the update with $apply() method on $scope

The simplest solution to this is to use Angular's $apply() method on the $scope. The $apply() method accepts a single argument, which is a function. The function executes, then the $digest() functionality will be executed for the variables that changed. We can change the trigger function, like this:


$scope.triggerChangeWithApply = function () {
setTimeout(function(){
console.log('First name being reset');
$scope.$apply(function(){
$scope.firstName = ''
}
)
}, 1000);
};

Instead of changing the $scope.firstName directly in the timeout function, it is done inside he $apply() function. The $apply() function makes sure that Angular's bindings update. Retest the code, and you'll see that after the timeout function runs, the UI is automatically updated. Play with the Plunker

What do you do when all your code is encapsulated into an Angular Service?

The original use case I mentioned was when a remote service responded with asynchronous events. If you're building a larger application, there is a good chance that your remote service code will be encapsulated out of a controller and into an AngularJS Service. AngularJS Services do not have access to a $scope service because they do not have a view. As such, you cannot directly use $apply() to update bindings. What do you do when you want to update the view in response to an event handler inside of an Angular service?

I have two solutions, and I'm not sure which is better. The first is to add a callback to the function which resides inside the controller. Pass the callback as an argument into the service, and execute the callback when you get results. The callback method can call $apply() on the $scope. Something like this would work:


var callback = function(){
$scope.$apply()
}

In this case, a function is not used as an argument to $apply(). Angular will automatically figure out everything that has changed. The second solution is to grab a UI Element and find the scope on that, something like this.


var element = angular.element($('#MyElementWithController'));
element.scope().$apply();

This method works, and prevents the need for a callback just to update, however I'm unclear if it is the best approach. Ideally you wouldn't want the services trying to access the HTML directly for any reason.

Final Thoughts

I've had a lot of fun with this, but don't have any other thoughts to share.

Did I mention that you should all check out the free AngularJS training books I wrote at training course on AngularJS? You can get some books I wrote for free.

Sign up for DotComIt's Monthly Technical Newsletter

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Johan's Gravatar Better options are: 1. Inject $rootScope into your service and use $rootScope.$apply(). 2. Better and recommended solution is to use $q and promises in your service methods. $q is wired up with the Angular digest cycle so no need to do anything with $apply().
# Posted By Johan | 7/30/14 2:20 PM
Jeffry Houser's Gravatar Johan,

I don't understand what your preferred solution is recommending to do.

You write it up as a blog post or put it in a Plunker or something to demonstrate it.

JH
# Posted By Jeffry Houser | 7/30/14 3:53 PM
Mariano Benedettini's Gravatar Thanks to this post I found out that I needed to use $apply to change a watched scope variable from within an event listener. Thanks.
# Posted By Mariano Benedettini | 8/14/14 7:25 PM
Matt's Gravatar Thanks! This was exactly what I needed. :)
# Posted By Matt | 10/20/14 4:41 PM
Matt's Gravatar I'm new to Angular and couldn't figure out why my callout results weren't being blinded to the DOM. Works now, thanks so much.
# Posted By Matt | 12/22/14 10:42 AM
Georg's Gravatar Thank you, Jeffry, very helpful!
I'm using the recent controller alias mechanism, which typically no longer requires $scope to be injected except if needed as in your example. Then, using a callback to update the variables in the controller, I have found it useful to angular.bind "this" of the controller to the callback (which may be obvious to some, I guess).
# Posted By Georg | 1/14/15 8:12 AM
markhandiah's Gravatar Oh man! You are the best! However I lost you on the two solutions inside an angular servce. Which is your recommended approach
# Posted By markhandiah | 1/20/15 2:00 PM
Jeffry Houser's Gravatar @markhandiah If you're doing it inside a service; I'd probably use the callback approach.
# Posted By Jeffry Houser | 1/20/15 2:36 PM
Sanny's Gravatar Hi Jeffry , thank you for this post . I have a async function which call network operation via $http in controller. After success callback receive the data , i updated it to scope var . Eventhough the $scope data are updated , it doesn't seem trigger the $apply so that the view won't changed . In order to trigger the digest , i put the $apply in callback as u said . But it is showing an error "$digest already in progress"
# Posted By Sanny | 1/30/15 2:50 AM
Jeffry Houser's Gravatar @Sanny

Without seeing the code; I'm not really sure. If you can put together a plunker example, that may help.

Or you can hire me as a personal mentor and we can jump on a call w/ a screenshare.
# Posted By Jeffry Houser | 1/30/15 7:24 AM
wips's Gravatar Johan, thanks, you've made my day! I've spent some significant time figuring out how digest cycle is fired in legacy code - the answer is $q.
# Posted By wips | 2/26/15 9:40 AM
Thx's Gravatar Thank you so much. It saved me a bunch of time.
Interesting is if you dont use the following code, the view gets updated as soon as you move the "cursor". Thats very annoying.

   setTimeout(function(){
         console.log('First name being reset');
         $scope.$apply(function(){
            
         }
         )
         }, 3000);
# Posted By Thx | 3/14/15 7:22 AM
Quinnland23's Gravatar Yes, awesome solution with the asynch calls not refreshing without the $apply call. Thank you!!!
# Posted By Quinnland23 | 5/13/15 6:50 PM
Binh Nguyen's Gravatar Thanks, nice post
# Posted By Binh Nguyen | 6/27/15 8:45 AM
Martin's Gravatar Thank you so much. Good example. I had this issue with combining KendoUI with angularjs. Angular obviously doesn't listen properly when Kendo is superimposed on the angular scope. A callback did the job. Callback seems neater because you don't mix too much with the DOM, except when you are in a angular directive and create the DOM elements yourself and put the KendoUI stuff on it.
Thanks again!
# Posted By Martin | 7/9/15 8:49 AM
Tomáš Bed?ich's Gravatar Thank you Jeffry for your article and Johan for your helpful comment!
# Posted By Tomáš Bed?ich | 11/21/15 7:58 AM
Wei Jiang's Gravatar Thank you Jeffry, very helpful!
# Posted By Wei Jiang | 12/28/15 9:44 PM
Jackson N's Gravatar Thankyou so much solved a lot o confusion on angular scope and data binding
# Posted By Jackson N | 2/14/16 11:10 PM
Elijah's Gravatar Great info. Had scratched the wall a lot :)
# Posted By Elijah | 6/3/16 7:05 AM
s9tpepper's Gravatar Don't use setTimeout, use $timeout, and you don't have to worry about $apply as it will do it for you unless you pass in a 3rd arg as false to skip dirty checking.

Re: to services, use $http or $resource and that should also take care of your dirty checking issues.
# Posted By s9tpepper | 7/5/16 4:30 PM
Fred's Gravatar From Thx:

setTimeout(function(){
console.log('First name being reset');
$scope.$apply(function(){

}
)
}, 3000);

First, the $scope.$apply is not necessary in this example and really should be avoided except in a directive link function which doesn't get 'digested'. The $timeout triggers a digest after the timeout ends!

Second, the 3000 is unnecessary and blocks execution for three seconds. The default is 0 ms which means an immediate $digest is triggered.

I have found that in 99+% of cases, my bindings not updating on a change was due to another angular error in the code.
# Posted By Fred | 4/17/17 6:49 PM
Harit Yadav's Gravatar Thanx a lot. Finally an expanation on why digest cycle was not triggered when updating data using calls in a service. :)
# Posted By Harit Yadav | 11/27/18 2:14 AM
All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.