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

时间:2014-03-12 09:24:56

标签: angularjs angularjs-scope

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

  

不要做(!$ scope。$$阶段)$ scope。$ apply(),这意味着你的$ scope。$ apply()在调用堆栈中不够高。< / p>

所以现在我有两个问题:

  1. 为什么这是一个反模式?
  2. 我如何安全地使用$ scope。$ apply?
  3. 另一个&#34;解决方案&#34;防止&#34;消化已在进行中#34;错误似乎是使用$ timeout:

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

    这是要走的路吗?它更安全吗?所以这是一个真正的问题:如何完全消除已经在进行中的消化的可能性&#34;错误?

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

6 个答案:

答案 0 :(得分:109)

经过多次挖掘后,我能够解决使用$scope.$apply是否始终安全的问题。简短的回答是肯定的。

答案很长:

由于您的浏览器执行Javascript的方式,两个摘要调用不可能偶然碰撞

  

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

因此错误&#34;摘要已在进行中&#34;只能在一种情况下发生:当在另一个$ apply中发出$ apply时,例如:

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

这种情况可以出现如果我们在纯非angularjs回调中使用$ scope.apply,例如setTimeout的回调。因此,以下代码是100%防弹,并且没有需要执行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,则根本不需要它:例如,在{j}回调中,例如setTimeout

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

答案 1 :(得分:16)

现在绝对是一种反模式。即使你检查了$$阶段,我也看到了消化的爆炸。您不应该访问由$$前缀表示的内部API。

你应该使用

 $scope.$evalAsync();

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

答案 2 :(得分:9)

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

$digest周期检查对象,即附加到$watch的模型(准确$scope),以评估其值是否已更改,如果检测到更改,则需要采取必要的步骤更新视图。

现在当你使用$scope.$apply时,你会遇到一个错误“已经在进行中”所以很明显$ digest正在运行但触发它的是什么?

ANS - &GT;每次$http次呼叫,所有点击,重复,显示,隐藏等都会触发$digest周期以及每个$ SCOPE的最差部分。

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

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

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

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

即使用户没有触发任何事件,仅仅点击或点击显示/隐藏也可能会触发超过100个属性的$digest周期!

答案 3 :(得分:9)

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

第一个

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

如果上述条件为真,那么您可以应用$ scope。$ apply otherwies not和

第二个解决方案是使用$ timeout

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

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

答案 4 :(得分:0)

使用$timeout,这是建议的方式。

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

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

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

答案 5 :(得分:-1)

我找到了非常酷的解决方案:

.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
        });
    }
])