首页 文章

为什么使用if(!$ scope . $$ phase)$ scope . $ apply()反模式?

提问于
浏览
90

有时我需要在我的代码中使用 $scope.$apply ,有时它会抛出"digest already in progress"错误 . 所以我开始找到解决方法并找到了这个问题:AngularJS : Prevent error $digest already in progress when calling $scope.$apply() . 但是在评论中(以及角度维基上),您可以阅读:

不要做if(!$ scope . $$阶段)$ scope . $ apply(),这意味着你的$ scope . $ apply()在调用堆栈中不够高 .

所以现在我有两个问题:

  • 为什么这是一个反模式呢?

  • 如何安全地使用$ scope . $ apply?

防止“正在进行摘要”错误的另一个“解决方案”似乎是使用$ timeout:

$timeout(function() {
  //...
});

这是要走的路吗?它更安全吗?所以这是真正的问题:我如何能够消除"digest already in progress"错误的可能性?

PS:我只使用$ scope . $ apply适用于非同步的非angularjs回调 . (据我所知,你必须使用$ scope . 如果你想要应用你的更改,请申请$)

6 回答

  • -1

    经过一番挖掘后,我能够解决使用 $scope.$apply 是否总是安全的问题 . 简短的回答是肯定的 .

    答案很长:

    由于您的浏览器如何执行Javascript,因此两个摘要调用不可能发生碰撞 by chance .

    我们编写的JavaScript代码并非一次全部运行,而是依次执行 . 这些转弯中的每一个都从头到尾不间断地运行,当转弯运行时,我们的浏览器中没有其他任何事情发生 . (来自http://jimhoskins.com/2012/12/17/angularjs-and-apply.html)

    因此,错误“已经在进行中的摘要”只能在一种情况下发生:当在另一个$ apply中发出$ apply时,例如:

    $scope.apply(function() {
      // some code...
      $scope.apply(function() { ... });
    });
    

    这种情况可能会出现 if 我们在纯非angularjs回调中使用$ scope.apply,例如 setTimeout 的回调 . 所以下面的代码是100%防弹,有 no 需要做 if (!$scope.$$phase) $scope.$apply()

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

    即使这个是安全的:

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

    什么是 NOT 安全(因为$ timeout - 就像所有angularjs助手一样 - 已经为你调用 $scope.$apply ):

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

    这也解释了为什么 if (!$scope.$$phase) $scope.$apply() 的使用是一种反模式 . 如果以正确的方式使用 $scope.$apply ,则根本不需要它:例如,在 setTimeout 之类的纯js回调中 .

    请阅读http://jimhoskins.com/2012/12/17/angularjs-and-apply.html以获取更详细的说明 .

  • 9

    现在绝对是一种反模式 . 我只是不应该访问 $$ 前缀表示的内部API .

    你应该用

    $scope.$evalAsync();
    

    因为这是Angular ^ 1.4中的首选方法,并且特别是作为应用程序层的API公开 .

  • 0

    在任何情况下,当您的摘要正在进行并且您推送其他服务进行摘要时,它只会出现错误,即摘要已在进行中 . 所以要治愈这个你有两个选择 . 您可以检查正在进行的任何其他摘要,如轮询 .

    First one

    if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
        $scope.$apply();
    }
    

    如果上述条件为真,则可以应用$ scope . $ apply otherwies not和

    second solution 使用$ timeout

    $timeout(function() {
      //...
    })
    

    它不会让其他摘要开始直到$ timeout完成它的执行 .

  • 9

    scope.$apply 触发 $digest 循环,这是双向数据绑定的基础

    $digest 循环检查对象,即附加到 $scope 的模型(确切地说是 $watch ),以评估它们的值是否已更改,如果它检测到更改,则需要采取必要步骤来更新视图 .

    现在当你使用 $scope.$apply 时遇到错误"Already in progress" so it is quite obvious that a $digest is running but what triggered it?

    ans - >每个 $http 次调用,所有ng-click,repeat,show,hide等触发 $digest 循环以及每个$ SC76E的最差部分 .

    即说你的页面有4个控制器或指令A,B,C,D

    如果每个属性中有4个 $scope 属性,则页面上总共有16个$ scope属性 .

    如果在控制器D中触发 $scope.$apply ,则 $digest 循环将检查所有16个值!加上所有$ rootScope属性 .

    Answer--> 但是 $scope.$digest 在子级和相同范围上触发 $digest ,因此它只会检查4个属性 . 因此,如果您确定D中的更改不会影响A,B,C,那么请使用 $scope.$diges t而不是 $scope.$apply .

    因此,即使用户有 not fired any event ,仅仅ng-click或ng-show / hide可能会触发超过100个属性的 $digest 循环!

  • 14

    使用 $timeout ,这是推荐的方式 .

    我的方案是我需要根据从WebSocket收到的数据更改页面上的项目 . 因为它在Angular之外,没有$ timeout,唯一的模型将被更改,但不会更改视图 . 因为Angular不知道那条数据已被更改 . $timeout 基本上告诉Angular在下一轮$ digest中进行更改 .

    我也尝试了以下内容并且有效 . 与我不同的是,$ timeout更清晰 .

    setTimeout(function(){
        $scope.$apply(function(){
            // changes
        });
    },0)
    
  • 109

    我发现非常酷的解决方案:

    .factory('safeApply', [function($rootScope) {
        return function($scope, fn) {
            var phase = $scope.$root.$$phase;
            if (phase == '$apply' || phase == '$digest') {
                if (fn) {
                    $scope.$eval(fn);
                }
            } else {
                if (fn) {
                    $scope.$apply(fn);
                } else {
                    $scope.$apply();
                }
            }
        }
    }])
    

    注入你需要的地方:

    .controller('MyCtrl', ['$scope', 'safeApply',
        function($scope, safeApply) {
            safeApply($scope); // no function passed in
            safeApply($scope, function() { // passing a function in
            });
        }
    ])
    

相关问题