When discussing the merits of AngularJS, two-way data binding is often touted as a major benefit of Angular over other JS frameworks. Digging deeper, the documentation suggests
I believe this is what happens. AngularJS made a smart assumption that model changes happen only on user interaction. These interactions can happen due to
AngularJS directives for the corresponding events wrap the expression execution in $scope.$apply as shown by @pixelbits in his example. This results in digest cycle.
There are some other events too where AngularJS triggers the digest loop. $timeout service and the $interval service are two such examples. Code wrapped in these service also results in digest loop to run. There maybe be some other events\services that can cause digest cycles to execute but these are the major ones.
This is the very reason that changes to model outside the Angular context does not update the watches and bindings. So one needs to explicitly call $scope.$apply. We do it all the time when integrating with jQuery plugins.