What gets added to $scope.$$watchers by default in Angular? And what triggers $digests?

后端 未结 2 446
粉色の甜心
粉色の甜心 2020-12-22 10:53

I\'m reading Build Your Own AngularJS and have a decent understanding of how $scopes, $watch and $digest work. I understand how it wor

相关标签:
2条回答
  • 2020-12-22 11:27

    It's my understanding that any two-way bindings get a $watch in their scope, however, if it is added by angular internals, you don't get that hook, because, for example, ngModelController has the callback, so you can't use that callback, I think what is on scope does not get a watch unless it is bound to the view.

    $digest is not used on model binding from what I found in the source code - but I found plenty of uses of $apply. In fact I found very few uses of $digest in the angular code at all. I didn't look in every file, but I did find it used in the expression parser here. Which I find interesting, but to answer your question, $digest in not called often, and $apply is called on only a few occasions with model binding, most notably on $commitViewValue() in the ngModelController. It also calls apply when an input is "touched"(ng-touched). I also found that $evalAsync calls $digest as well.

    What I found for certain on apply/digest:

    //apply method in rootScope
    $apply: function(expr) {
        try {
          beginPhase('$apply');
          return this.$eval(expr);
        } catch (e) {
          $exceptionHandler(e);
        } finally {
          clearPhase();
          try {
            $rootScope.$digest();
          } catch (e) {
            $exceptionHandler(e);
            throw e;
          }
        }
      }
    

    As you can see, apply is really just a safe wrap around $digest, so one might argue to use $apply over digest. According to the source $digest does some crazy while-loop $scope traversal.

    TLDR;

    The notes from the angular team on digest:

    Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the $digest() keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are firing. This means that it is possible to get into an infinite loop. This function will throw 'Maximum iteration limit exceeded.' if the number of iterations exceeds 10.

    Usually, you don't call $digest() directly in {@link ng.directive:ngController controllers} or in {@link ng.$compileProvider#directive directives}. Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a {@link ng.$compileProvider#directive directive}), which will force a $digest().

    If you want to be notified whenever $digest() is called, you can register a watchExpression function with {@link ng.$rootScope.Scope#$watch $watch()} with no listener.

    0 讨论(0)
  • 2020-12-22 11:33

    Some of the common directives that use $watch / $watchCollection / $watchGroup internally:

    1. ng-model
    2. ng-bind / {{ }}
    3. ng-show & ng-hide
    4. ng-class
    5. ng-repeat
    6. ng-if
    7. ng-switch
    8. ng-include

    Note that the only one that sets up a two-way binding is ng-model (scope -> view & view -> scope).

    The others set up a one-way binding (scope -> view).

    Simply exposing something on for example a controller´s $scope will not add a watcher.

    For example, the following will not result in a watcher being added:

    angular.module('myApp', []).controller('Controller', function MyCtrl($scope) {
      $scope.value = 1;
    });
    

    Together with:

    <body ng-app="myApp" ng-controller="Controller">
    </body>
    

    But if you replace the HTML with the following one watcher will be added:

    <body ng-app="myApp" ng-controller="Controller">
      <div>{{value}}</div>
    </body>
    

    Some common scenarios when the digest cycle is triggered:

    1. When ng-click is evaluated
    2. When ng-model changes (for example when typing in an input)
    3. By the $http service
    4. In $timeout and $interval

    Note that there is one big difference between $apply and $digest:

    Calling scope.$digest() will execute the watchers only on that scope and its children.

    Calling scope.$apply() will trigger $digest on the $rootScope, which means all the scopes will be traversed and all watchers executed.

    $apply also accepts an expression as an argument. This expression will be evaluated inside a try-catch statement and any exception will be passed on to the $exceptionHandler service.

    $digest does not accept any arguments.

    Usually you only call $digest instead of $apply when you are chasing micro optimizations and really know what you are doing.

    0 讨论(0)
提交回复
热议问题