ChatGPT解决这个技术问题 Extra ChatGPT

AngularJS:在调用 $scope.$apply() 时防止错误 $digest 已经在进行中

我发现自从以 Angular 构建应用程序以来,我越来越需要手动将页面更新到我的范围。

我知道这样做的唯一方法是从我的控制器和指令的范围内调用 $apply()。这样做的问题是它不断向控制台抛出错误,内容如下:

错误:$digest 已经在进行中

有谁知道如何避免此错误或以不同的方式实现相同的目标?

我们需要越来越多地使用 $apply 真是令人沮丧。
即使我在回调中调用 $apply,我也会收到此错误。我正在使用第三方库来访问他们服务器上的数据,所以我不能利用 $http,我也不想这样做,因为我必须重写他们的库才能使用 $http。
使用$timeout()
使用 $timeout(fn) + 1,它可以解决问题,!$scope.$$phase 不是最好的解决方案。
仅包装代码/调用范围。$apply 来自 within 超时(不是 $timeout)AJAX 函数(不是 $http)和事件(不是 ng-*)。确保,如果您是从函数中调用它(通过 timeout/ajax/events 调用),那么它不是最初在加载时运行。

b
betaorbust

根据最近与 Angular 人员就这个主题进行的讨论:出于面向未来的原因,您不应使用 $$phase

当按下“正确”的方法时,答案是目前

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

我最近在编写 Angular 服务来包装 facebook、google 和 twitter API 时遇到了这个问题,这些 API 在不同程度上都有回调。

这是服务中的一个示例。 (为简洁起见,服务的其余部分——设置变量、注入 $timeout 等——已被取消。)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

请注意,$timeout 的延迟参数是可选的,如果未设置,则默认为 0($timeout 调用 $browser.defer 其中 defaults to 0 if delay isn't set

有点不直观,但这是编写 Angular 的人的答案,所以对我来说已经足够了!


我在我的指令中多次遇到这种情况。正在为 redactor 编写一个,结果证明效果很好。我在一次与 Brad Green 的聚会上,他说 Angular 2.0 将是巨大的,使用 JS 的本机观察能力和使用 polyfill 的浏览器没有摘要循环。到那时,我们将不再需要这样做。 :)
昨天我看到一个问题,在 $timeout 中调用 selectize.refreshItems() 会导致可怕的递归摘要错误。有什么想法吗?
如果您使用 $timeout 而不是原生 setTimeout,为什么不使用 $window 而不是原生 window
@LeeGee:在这种情况下使用 $timeout 的重点是 $timeout 确保正确更新角度范围。如果 $digest 不在进行中,它将导致新的 $digest 运行。
@webicy 那不是一回事。当传递给 $timeout 的函数体运行时,promise 已经解决了!绝对没有理由cancel它。来自 docs:“因此,承诺将通过拒绝来解决。”您无法解决已解决的承诺。您的取消不会导致任何错误,但也不会产生任何积极的影响。
D
Dan Atkinson

不要使用这种模式——这最终会导致比它解决的更多的错误。即使您认为它修复了某些问题,但它没有。

您可以通过检查 $scope.$$phase 来检查 $digest 是否已经在进行中。

if(!$scope.$$phase) {
  //$digest or $apply
}

如果 $digest$apply 正在进行,$scope.$$phase 将返回 "$digest""$apply"。我相信这些状态之间的区别在于 $digest 将处理当前范围及其子范围的监视程序,而 $apply 将处理所有范围的监视程序。

就@dnc253 而言,如果您发现自己经常调用$digest$apply,那么您可能做错了。当我需要更新作用域的状态时,我通常发现我需要消化,因为 DOM 事件在 Angular 范围之外触发。例如,当 twitter 引导模式被隐藏时。有时 DOM 事件会在 $digest 正在进行时触发,有时则不会。这就是我使用此检查的原因。

如果有人知道,我很想知道一种更好的方法。

来自评论:@anddoutoi

angular.js Anti Patterns

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


在我看来, $digest / $apply 默认应该这样做
请注意,在某些情况下,我必须检查当前范围和根范围。我在根目录上获得了 $$phase 的价值,但在我的范围内却没有。认为它与指令的隔离范围有关,但是..
"停止做if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
@anddoutoi:同意;你的链接很清楚这不是解决方案;但是,我不确定“你在调用堆栈中不够高”是什么意思。你知道这意味着什么吗?
@threed:请参阅 aaronfrost 的答案。正确的做法是在下一个循环中使用 defer 触发摘要。否则事件将丢失并且根本不会更新范围。
M
Marwane Boudriga

摘要循环是一个同步调用。在完成之前,它不会控制浏览器的事件循环。有几种方法可以解决这个问题。解决这个问题的最简单方法是使用内置的 $timeout,第二种方法是如果您使用下划线或 lodash(您应该使用),请调用以下命令:

$timeout(function(){
    //any code in here will automatically have an apply run afterwards
});

或者如果你有 lodash:

_.defer(function(){$scope.$apply();});

我们尝试了几种解决方法,我们讨厌将 $rootScope 注入我们所有的控制器、指令甚至一些工厂。所以,到目前为止,$timeout 和 _.defer 是我们最喜欢的。这些方法成功地告诉 angular 等到下一个动画循环,这将保证当前的 scope.$apply 结束。


这可以与使用 $timeout(...) 相提并论吗?我在几种情况下使用了 $timeout 来推迟到下一个事件周期,它似乎工作正常——任何人都知道是否有理由不使用 $timeout?
仅当您已经在使用 underscore.js 时才应该使用它。这个解决方案不值得为了使用它的 defer 函数而导入整个下划线库。我更喜欢 $timeout 解决方案,因为每个人都已经可以通过 Angular 访问 $timeout,而不依赖于其他库。
没错...但是如果您不使用下划线或 lodash... 您需要重新评估您在做什么。这两个库改变了代码的外观。
我们将 lodash 作为 Restangular 的依赖项(我们将很快消除 Restangular 以支持 ng-route)。我认为这是一个很好的答案,但假设人们想要使用下划线/lodash 并不是很好。无论如何,这些库都很好……如果你充分利用它们……这些天我使用 ES5 方法消除了我过去包含下划线的 98% 的原因。
你是对的@SgtPooki。我修改了答案以包括使用 $timeout 的选项。 $timeout 和 _.defer 都会等到下一个动画循环,这将确保当前的 scope.$apply 已经结束。感谢您让我诚实,并让我在这里更新答案。
o
ojii

这里的许多答案都包含很好的建议,但也可能导致混淆。简单地使用 $timeout 不是最好也不是正确的解决方案。此外,如果您担心性能或可扩展性,请务必阅读。

你应该知道的事情

$$phase 是框架私有的,这是有充分理由的。

$timeout(callback) 将等到当前摘要周期(如果有)完成,然后执行回调,然后在最后运行完整的 $apply。

$timeout(callback, delay, false) 会做同样的事情(在执行回调之前有一个可选的延迟),但不会触发 $apply (第三个参数),如果你没有修改你的 Angular 模型($scope )。

$scope.$apply(callback) 会调用 $rootScope.$digest 等,这意味着它会重新消化应用程序的根范围及其所有子范围,即使您处于隔离范围内。

$scope.$digest() 只会将其模型同步到视图,但不会消化其父范围,这可以在使用隔离范围(主要来自指令)处理 HTML 的隔离部分时节省大量性能. $digest 不接受回调:您执行代码,然后进行摘要。

$scope.$evalAsync(callback) 已在 angularjs 1.2 中引入,可能会解决您的大部分问题。请参阅最后一段以了解更多信息。

如果你得到 $digest already in progress 错误,那么你的架构是错误的:要么你不需要重新消化你的范围,要么你不应该负责(见下文)。

如何构建你的代码

当您收到该错误时,您正在尝试消化您的示波器,而它已经在进行中:因为您不知道此时您的示波器的状态,您不负责处理它的消化。

function editModel() {
  $scope.someVar = someVal;
  /* Do not apply your scope here since we don't know if that
     function is called synchronously from Angular or from an
     asynchronous code */
}

// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
  // No need to digest
  editModel();
}

// Any kind of asynchronous code, for instance a server request
callServer(function() {
  /* That code is not watched nor digested by Angular, thus we
     can safely $apply it */
  $scope.$apply(editModel);
});

而且,如果您知道自己在做什么,并且在一个大型 Angular 应用程序的一部分中处理一个孤立的小指令,那么您可能更喜欢 $digest 而不是 $apply 以节省性能。

自 Angularjs 1.2 起更新

任何 $scope: $evalAsync 中都添加了一个新的、强大的方法。基本上,如果正在发生,它将在当前摘要周期内执行其回调,否则新的摘要周期将开始执行回调。

如果您真的知道只需要同步 HTML 的一个孤立部分(因为如果没有正在进行的操作将触发一个新的 $apply),那仍然不如 $scope.$digest,但这是最好的解决方案当你执行一个函数时,你不知道它是否会同步执行,例如在获取可能缓存的资源之后:有时这需要对服务器进行异步调用,否则资源将本地同步获取。

在这些情况下以及您拥有 !$scope.$$phase 的所有其他情况下,请务必使用 $scope.$evalAsync( callback )


$timeout 被批驳。你能给出更多避免 $timeout 的理由吗?
$scope.applyAsync() 怎么样 - 没有人在这个 OP 的任何答案中提到这个,但我在其他线程中看到过。当我将所有的 $scope.apply() 更改为 scope.applyAsync() 时,我所有的 $digest 循环错误都消失了……不知道我是否给自己带来了更多问题,但到目前为止还没有问题。
l
lambinator

方便的小助手方法来保持这个过程干燥:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}

您的 safeApply 比其他任何事情更能帮助我了解发生了什么。感谢您发布。
我正准备做同样的事情,但这样做是否意味着我们在 fn() 中所做的更改可能不会被 $digest 看到?假设 scope.$$phase === '$digest' 延迟函数不是更好吗?
我同意,有时 $apply() 用于触发摘要,只是自己调用 fn ......这不会导致问题吗?
我觉得 scope.$apply(fn); 应该是 scope.$apply(fn()); 因为 fn() 将执行函数而不是 fn。请帮我解决我错的地方
@ZenOut 对 $apply 的调用支持许多不同类型的参数,包括函数。如果传递了一个函数,它会评估该函数。
M
Mistalis

我在使用第三方脚本(例如 CodeMirror 和 Krpano)时遇到了同样的问题,甚至使用此处提到的 safeApply 方法也没有为我解决错误。

但是解决它的方法是使用 $timeout 服务(不要忘记先注入它)。

因此,类似:

$timeout(function() {
  // run my code safely here
})

如果在你的代码中你正在使用

这个

也许是因为它在工厂指令的控制器内,或者只需要某种绑定,那么你会做类似的事情:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)

T
Trevor

请参阅http://docs.angularjs.org/error/$rootScope:inprog

当您调用 $apply 时会出现问题,该调用有时在 Angular 代码之外异步运行(应使用 $apply 时),有时在 Angular 代码内部同步运行(这会导致 $digest already in progress 错误)。

例如,当您有一个从服务器异步获取项目并缓存它们的库时,可能会发生这种情况。第一次请求一个项目时,它将被异步检索,以免阻塞代码执行。但是,第二次,该项目已经在缓存中,因此可以同步检索。

防止此错误的方法是确保调用 $apply 的代码异步运行。这可以通过在对 $timeout 的调用中运行您的代码并将延迟设置为 0(这是默认设置)来完成。但是,在 $timeout 中调用您的代码就不必再调用 $apply,因为 $timeout 会自行触发另一个 $digest 循环,而这又会进行所有必要的更新等。

解决方案

简而言之,而不是这样做:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

做这个:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

仅当您知道运行它的代码将始终在 Angular 代码之外运行时才调用 $apply(例如,您对 $apply 的调用将发生在由您的 Angular 代码之外的代码调用的回调中)。

除非有人意识到使用 $timeout 而不是 $apply 的某些不利影响,否则我不明白为什么您不能总是使用 $timeout(零延迟)而不是 $apply,因为它会做大约一样。


谢谢,这适用于我自己没有打电话给 $apply 但仍然收到错误的情况。
主要区别在于 $apply 是同步的(执行它的回调,然后执行 $apply 之后的代码),而 $timeout 不是:执行超时之后的当前代码,然后一个新堆栈以其回调开始,就像你正在使用 setTimeout。如果您更新两次相同的模型,这可能会导致图形故障:$timeout 将等待视图刷新,然后再次更新它。
真的,谢谢,三。由于某些 $watch 活动,我有一个方法被调用,并试图在我的外部过滤器完成执行之前更新 UI。把它放在一个 $timeout 函数中对我有用。
d
dnc253

当您收到此错误时,基本上意味着它已经在更新您的视图。您确实不需要在控制器中调用 $apply()。如果您的视图没有按预期更新,然后在调用 $apply() 后收到此错误,则很可能意味着您没有正确更新模型。如果您发布一些细节,我们可以找出核心问题。


呵呵,我花了一整天的时间才发现 AngularJS 不能“神奇地”观看绑定,我有时应该用 $apply() 来推动他。
you're not updating the the model correctly到底是什么意思? $scope.err_message = 'err message'; 更新不正确?
唯一需要调用 $apply() 的时间是在 Angular“外部”更新模型时(例如,来自 jQuery 插件)。很容易陷入视图不正确的陷阱,因此您到处扔一堆 $apply(),然后最终导致 OP 中出现错误。当我说 you're not updating the the model correctly 时,我的意思是所有业务逻辑都没有正确填充范围内的任何内容,这导致视图看起来不像预期的那样。
@dnc253 我同意,我写了答案。知道我现在所知道的,我会使用 $timeout(function(){...});它的作用与 _.defer 相同。它们都遵循下一个动画循环。
A
Anik Islam Abhi

安全 $apply 的最短形式是:

$timeout(angular.noop)

C
CMCDragonkai

您也可以使用 evalAsync。它将在摘要完成后的某个时间运行!

scope.evalAsync(function(scope){
    //use the scope...
});

M
Markus

首先,不要这样修复

if ( ! $scope.$$phase) { 
  $scope.$apply(); 
}

这是没有意义的,因为 $phase 只是 $digest 循环的布尔标志,因此您的 $apply() 有时不会运行。请记住,这是一种不好的做法。

而是使用 $timeout

    $timeout(function(){ 
  // Any code in here will automatically have an $scope.apply() run afterwards 
$scope.myvar = newValue; 
  // And it just works! 
});

如果你使用下划线或lodash,你可以使用defer():

_.defer(function(){ 
  $scope.$apply(); 
});

C
Community

如果您使用这种方式(https://stackoverflow.com/a/12859093/801426),有时您仍然会收到错误。

尝试这个:

if(! $rootScope.$root.$$phase) {
...

同时使用 !$scope.$$phase 和 !$scope.$root.$$phase (不是 !$rootScope.$root.$$phase)对我有用。 +1
$rootScopeanyScope.$root 是同一个人。 $rootScope.$root 是多余的。
A
Anik Islam Abhi

您应该根据上下文使用 $evalAsync 或 $timeout。

这是一个很好的解释的链接:

http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm


e
eHayik

尝试使用

$scope.applyAsync(function() {
    // your code
});

代替

if(!$scope.$$phase) {
  //$digest or $apply
}

$applyAsync 将 $apply 的调用安排在以后发生。这可用于将需要在同一摘要中评估的多个表达式排队。

注意:在 $digest 中,$applyAsync() 仅在当前范围是 $rootScope 时才会刷新。这意味着如果您在子作用域上调用 $digest,它不会隐式刷新 $applyAsync() 队列。

示例:

  $scope.$applyAsync(function () {
                if (!authService.authenticated) {
                    return;
                }

                if (vm.file !== null) {
                    loadService.setState(SignWizardStates.SIGN);
                } else {
                    loadService.setState(SignWizardStates.UPLOAD_FILE);
                }
            });

参考:

1.Scope.$applyAsync() vs. Scope.$evalAsync() in AngularJS 1.3

AngularJs 文档


n
nelsonomuto

我建议您使用自定义事件而不是触发摘要循环。

我发现广播自定义事件并为此事件注册侦听器是触发您希望发生的动作的一个很好的解决方案,无论您是否处于摘要周期中。

通过创建自定义事件,您还可以更高效地使用代码,因为您只触发订阅所述事件的侦听器,而不像调用 scope.$apply 那样触发绑定到范围的所有监视。

$scope.$on('customEventName', function (optionalCustomEventArguments) {
   //TODO: Respond to event
});


$scope.$broadcast('customEventName', optionalCustomEventArguments);

A
Anik Islam Abhi

yearofmoo 在为我们创建可重用的 $safeApply 函数方面做得很好:

https://github.com/yearofmoo/AngularJS-Scope.SafeApply

用法 :

//use by itself
$scope.$safeApply();

//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);

//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {

});

//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {

});

//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);

C
Charley B.

我已经能够通过在我知道 $digest 函数将运行的地方调用 $eval 而不是 $apply 来解决这个问题。

根据 docs$apply 基本上是这样做的:

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

在我的例子中,ng-click 会更改范围内的变量,而该变量上的 $watch 会更改必须为 $applied 的其他变量。最后一步会导致错误“正在消化”。

通过在监视表达式中将 $apply 替换为 $eval,范围变量会按预期更新。

因此,看来,如果由于 Angular 中的一些其他更改而无论如何要运行摘要,那么您需要做的就是$eval


A
Anik Islam Abhi

改用 $scope.$$phase || $scope.$apply();


p
paulmelnikow

了解 Angular 文档调用检查 $$phaseanti-pattern,我尝试让 $timeout_.defer 工作。

timeout 和 deferred 方法会在 dom 中创建一个未解析的 {{myVar}} 内容,如 FOUT。对我来说这是不可接受的。它让我没有太多可以教条地告诉我某些东西是黑客,并且没有合适的替代方案。

唯一每次都有效的是:

if(scope.$$phase !== '$digest'){ scope.$digest() }

我不明白这种方法的危险性,也不明白为什么评论和角度团队中的人将其描述为黑客行为。该命令似乎精确且易于阅读:

“做摘要,除非已经发生了”

在 CoffeeScript 中它更漂亮:

scope.$digest() unless scope.$$phase is '$digest'

这有什么问题?是否有不会创建 FOUT 的替代方案? $safeApply 看起来不错,但也使用 $$phase 检查方法。


我很想看到对这个问题的知情答复!
这是一个 hack,因为这意味着您错过了上下文或不理解此时的代码:要么您处于角度摘要周期内并且您不需要它,要么您异步地在它之外然后您需要它。如果您在代码的那一点不知道,那么您不负责消化它
r
ranbuch

这是我的实用程序服务:

angular.module('myApp', []).service('Utils', function Utils($timeout) {
    var Super = this;

    this.doWhenReady = function(scope, callback, args) {
        if(!scope.$$phase) {
            if (args instanceof Array)
                callback.apply(scope, Array.prototype.slice.call(args))
            else
                callback();
        }
        else {
            $timeout(function() {
                Super.doWhenReady(scope, callback, args);
            }, 250);
        }
    };
});

这是它的用法示例:

angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
    $scope.foo = function() {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.foo);

    $scope.fooWithParams = function(p1, p2) {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};

A
Ashu

我一直在使用这种方法,它似乎工作得很好。这只是等待循环完成的时间,然后触发 apply()。只需从您想要的任何地方调用函数 apply(<your scope>)

function apply(scope) {
  if (!scope.$$phase && !scope.$root.$$phase) {
    scope.$apply();
    console.log("Scope Apply Done !!");
  } 
  else {
    console.log("Scheduling Apply after 200ms digest cycle already in progress");
    setTimeout(function() {
        apply(scope)
    }, 200);
  }
}

j
julianm

当我禁用 debugger 时,错误不再发生。就我而言,这是因为调试器停止了代码执行。


S
Shawn Dotey

类似于上面的答案,但这对我来说非常有效......在服务中添加:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };

S
Sachin Mishra

问题基本上来了,我们要求有角度来运行摘要循环,即使它正在进行中,这会产生角度理解的问题。控制台中的后果异常。 1. 在 $timeout 函数内部调用 scope.$apply() 没有任何意义,因为它在内部也是如此。 2. 代码与 vanilla JavaScript 函数一起使用,因为它的本机不是角度定义的,即 setTimeout 3. 为此,您可以使用 if(!scope.$$phase){ scope.$evalAsync(function(){ }); }


S
Sergey Sahakyan
        let $timeoutPromise = null;
        $timeout.cancel($timeoutPromise);
        $timeoutPromise = $timeout(() => {
            $scope.$digest();
        }, 0, false);

这是避免此错误并避免 $apply 的好解决方案

如果基于外部事件调用,您可以将其与 debounce(0) 结合使用。以上是我们正在使用的“去抖动”,以及完整的代码示例

.factory('debounce', [
    '$timeout',
    function ($timeout) {

        return function (func, wait, apply) {
            // apply default is true for $timeout
            if (apply !== false) {
                apply = true;
            }

            var promise;
            return function () {
                var cntx = this,
                    args = arguments;
                $timeout.cancel(promise);
                promise = $timeout(function () {
                    return func.apply(cntx, args);
                }, wait, apply);
                return promise;
            };
        };
    }
])

以及代码本身来监听一些事件并仅在您需要的 $scope 上调用 $digest

        let $timeoutPromise = null;
        let $update = debounce(function () {
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
        }, 0, false);

        let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
            $update();
        });


        $scope.$on('$destroy', () => {
            $timeout.cancel($update);
            $timeout.cancel($timeoutPromise);
            $unwatchModelChanges();
        });

J
J.Hpour

您可以使用 $timeout 来防止错误。

$timeout(function () {
    var scope = angular.element($("#myController")).scope();
    scope.myMethod(); 
    scope.$scope();
}, 1);

如果我不想使用 $timeout 怎么办
W
Warren Davis

发现:https://coderwall.com/p/ngisma 其中 Nathan Walker(靠近页面底部)建议在 $rootScope 中使用装饰器来创建 func 'safeApply',代码:

yourAwesomeModule.config([
  '$provide', function($provide) {
    return $provide.decorator('$rootScope', [
      '$delegate', function($delegate) {
        $delegate.safeApply = function(fn) {
          var phase = $delegate.$$phase;
          if (phase === "$apply" || phase === "$digest") {
            if (fn && typeof fn === 'function') {
              fn();
            }
          } else {
            $delegate.$apply(fn);
          }
        };
        return $delegate;
      }
    ]);
  }
]);

e
eebbesen

这将解决您的问题:

if(!$scope.$$phase) {
  //TODO
}

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