【问题标题】:$scope.$watch doesn't trigger on every change$scope.$watch 不会在每次更改时触发
【发布时间】:2017-09-05 06:32:14
【问题描述】:

我正在使用 angularJS 1.4.8,昨天我注意到 $scope.$watch 不会触发每次导致我的应用程序出现错误的更改。

有没有办法强制它立即对每个更改起作用? 就像在这段代码中一样,在消息的每次更改中,我都希望手表中的函数触发:

(function(){

  angular.module('myApp', [])
  .controller('myAppController', myAppController)

  function myAppController($scope){
    console.log('controller loading !');
    $scope.message = 'message1';

    $scope.$watch('message', function(newMessage){
      console.log('newMessage', newMessage)
    });

    function changeMessage(){
      $scope.message='hi';
      $scope.message='hi12';
    }

    changeMessage(); 
  }

})();

控制台将打印:

controller loading !
newMessage hi22

plunker 链接https://plnkr.co/edit/SA1AcIVwr04uIUQFixAO?p=preview

编辑: 我真的很想知道除了用超时包装更改并使用范围应用之外,是否还有其他方法,在我的原始代码中,我在多个地方更改范围属性,我想避免每次更改都使用这个。

【问题讨论】:

  • 检查this
  • @Durga iv'e 多个地方,我在原始代码中更改了我的对象,除了将所有内容包装在超时范围内之外,还有其他方法吗?
  • 我认为是因为 $watch 函数仅在 angular 完成编译后激活。因此,当您更改$scope 时,在控制器初始化后,$watch 将触发。
  • 您只需要message 更改scope 来触发侦听器吗?
  • Why is $watch not firing的可能重复

标签: javascript angularjs


【解决方案1】:

发生这种情况是因为只有在摘要循环“之间”更改值时才会触发监视。

您的函数正在更改同一函数范围内的消息值。这将在同一个摘要循环中执行。 当角度移动到下一个循环时,它只会看到最后更改的值,在您的情况下将是 hi22

Here's 一篇很好的文章,让这种行为变得清晰

【讨论】:

    【解决方案2】:

    更新您的 changeMes​​sage 函数,使其使用 $scope.$apply 函数,这将确保您的更改得到反映,并且 Angular 知道您对变量的更改。

    changeMessage() {
       setTimeout(function () {
            $scope.$apply(function () {
              $scope.message = "Timeout called!";
            });
        }, 2000);
    }
    

    【讨论】:

    • 在应用范围内扭曲代码是最佳实践,因为它可以确保更新您的更改,完全不建议跳过此操作。正如您所说,在您的代码中,如果您的对象正在被修改,那么您有多个实例,这对您来说是一种理想的方式,我建议您编写一个执行此任务的函数。您可以阅读更多关于适用范围以及为什么在此处使用它有用jimhoskins.com/2012/12/17/angularjs-and-apply.html
    • 为什么需要setTimeout$apply?你可以用$timeout 做同样的事情而不用$apply,反之亦然。 $apply 正在运行 $digest$timeout 最后做同样的事情。你可以在这里阅读:codingeek.com/angularjs/…
    • @Kindzoku 我们需要 setTimeout 和 apply 因为如果我们不使用 setTimeout 那么很有可能当前的摘要周期正在进行中,我们可能会在进行中更改变量,最终会导致错误。使用 setTimeout 确保我们的更改总是在当前摘要周期完成后进行。
    • @Kindzoku,你是对的 2 秒延迟并不能保证 $digest 正在进行中,它与 2 秒无关,它甚至可以是 100 毫秒,这个工作优雅的事实是该角度只会在当前 $digest 周期完成后处理 setTimeout 中的代码换行
    • 这确实不是将更改应用到$scope 的好方法。更好的是使用 $timeout 函数中的构建。
    【解决方案3】:

    如果您将值更改为相同的摘要循环,则不会触发观察程序并采用最后一个值。当我们运行$timeout 时,我们会在下一个摘要循环中更改$scope.message 的值,并且观察者会按预期捕获它。

    看看简单的测试:

     $scope.$watch(function(){
      console.log('trigger');
      return $scope.message;
    },
      function(newMessage){
      console.log('newMessage', newMessage)
    });
    
    function changeMessage(){
      $scope.message='hi';
    
      $timeout(function(){
        $scope.message='hi12';
      });      
    }
    

    输出:

    controller loading !
     trigger
     newMessage hi
     trigger
     trigger
     newMessage hi12
     trigger
    

    【讨论】:

      【解决方案4】:

      不需要在 setTimeout 和 $apply 中同时包裹 changeMes​​sage。如果您需要在执行前跳过一些时间,只需使用:

      function changeMessage(){
          $timeout(function(){
              $scope.message = 'message';
          }/* or add time here, doesn't matter */);
      }
      

      或者只是:

      function changeMessage(){
          $scope.message = 'message';
          $scope.$apply();
      }
      

      这两种方法最后都调用$rootScope.$digest。这里有更多信息:https://www.codingeek.com/angularjs/angular-js-apply-timeout-digest-evalasync/

      【讨论】:

        【解决方案5】:

        $watch() 仅在每个$digest() 之间触发。

        Detailed explaination about the $apply() and $digest()

        在您的情况下,您在当前的$digest() 周期中不断更新$scope.message

        您可以通过使用$apply() 将每个新值应用于$scope 来更改它。就像@Ajinkya 写的那样。 唯一的问题是将 2000 毫秒设置为超时,并不能始终确保它在 $digest() 之后执行。最重要的是,Angular 有一个内置的超时功能。见下文。

        (function(){
          
          angular.module('myApp', [])
          .controller('myAppController', myAppController)
          
          function myAppController($scope, $timeout){
            console.log('controller loading !');
            $scope.message = 'message1';
            
            $scope.$watch('message', function(newMessage){
              console.log('newMessage', newMessage)
            });
            
            function changeMessage(){
            setTimeout(function () {
                $scope.$apply(function () {
                  $scope.message='hi12';
                });
            }, 2000);
              
            }
            
            changeMessage(); 
          }
          
        })();
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
        
        <div ng-app="myApp" ng-controller="myAppController"></div>

        解决方案

        最好的方法是在$timeout 函数中调用构建,而不以毫秒为单位设置时间。

        这样,角度始终确保$timeout 将在最新的$digest() 之后运行。最重要的是。您不必使用$scope.$apply()。因为$timeout 已经运行了$digest(),其中$scope.$apply() 正在手动调用一个新的$diggest() 循环。

        (function(){
          
          angular.module('myApp', [])
          .controller('myAppController', myAppController)
          
          function myAppController($scope, $timeout){
            console.log('controller loading !');
            $scope.message = 'message1';
            
            $scope.$watch('message', function(newMessage){
              console.log('newMessage', newMessage)
            });
            
            function changeMessage(){
                $timeout(function () {
                    $scope.message='hi12';
                });
              
            }
            
            changeMessage(); 
          }
          
        })();
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
        
            <div ng-app="myApp" ng-controller="myAppController"></div>

        【讨论】:

          猜你喜欢
          • 2016-03-07
          • 2014-07-20
          • 2016-06-19
          • 2013-07-21
          • 2016-03-10
          • 1970-01-01
          • 1970-01-01
          • 2015-09-03
          • 1970-01-01
          相关资源
          最近更新 更多