AngularJS : Prevent error $digest already in progress when calling $scope.$apply()
我发现,自从在Angular中构建应用程序以来,我需要越来越多地手动将页面更新到我的范围中。
我知道的唯一方法是从我的控制器和指令的范围内调用
Error: $digest already in progress
有人知道如何避免这个错误,或者以不同的方式实现相同的事情吗?
从最近一次关于这个问题的讨论中可以看出:出于未来的考虑,你不应该使用
当按"正确"方式时,答案当前为
1 2 3 | $timeout(function() { // anything you want can go here and will safely be run on the next digest. }) |
我最近在写Angular服务来包装Facebook、Google和TwitterAPI时遇到了这个问题,这些API在不同程度上都有回调。
下面是一个来自服务内部的示例。(为了简洁起见,服务的其余部分——设置变量、注入$timeout等——都被省略了。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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的delay参数是可选的,如果不设置delay,则默认为0($timeout调用$browser.defer,如果不设置delay,则默认为0)
有点不直观,但这是从写角度的家伙的答案,所以这对我来说已经足够好了!
Don't use this pattern - This will end up causing more errors than it solves. Even though you think it fixed something, it didn't.
您可以通过检查
1 2 3 | if(!$scope.$$phase) { //$digest or $apply } |
如果
就@dnc253而言,如果您经常打电话给
如果有人知道的话,我想知道更好的方法。
从评论:用"@"和"道多"
Angular.js反模式
Don't do if (!$scope.$$phase) $scope.$apply() , it means your$scope.$apply() isn't high enough in the call stack.
摘要循环是同步调用。在浏览器的事件循环完成之前,它不会对其进行控制。有几种方法可以解决这个问题。处理这一问题的最简单方法是使用内置的$timeout,第二种方法是,如果您使用下划线或lodash(应该使用),请调用以下代码:
1 2 3 | $timeout(function(){ //any code in here will automatically have an apply run afterwards }); |
或者如果您有下划线:
1 | _.defer(function(){$scope.$apply();}); |
我们尝试了几个解决方法,但我们不喜欢将$rootscope注入到我们的所有控制器、指令甚至一些工厂中。所以,到目前为止,$timeout和uu.defer一直是我们的最爱。这些方法成功地告诉Angular等待下一个动画循环,这将保证当前的作用域。$apply结束。
我在这里包含的意见和答案,但可以导致混乱。简单的使用是最好的
你应该知道的事情
$$phase 是私人的框架和有好的那个原因。摘要
$timeout(callback) 想等到当前周期(房间)是完成,然后执行的回调,然后运行一个完整的$apply 尽头。$timeout(callback, delay, false) (想做同样的一个可选的回调函数执行前的延迟),但不到三$apply 火灾性能参数)是特殊的,如果你没有在你的角模型($范围)。$scope.$apply(callback) invokes,在其他的事情,$rootScope.$digest ,这意味着它会redigest根的应用范围的信息和所有的儿童,即使你在考虑的范围之内。$scope.$digest() 想简单的视图模型来同步其父母,但不消化它的范围,这可以节省很多的表演,当工作在你的HTML的一个孤立的部分隔离的范围(从一个指令…)。摘要不花一美元:你的代码执行的回调,然后消化。介绍
$scope.$evalAsync(callback) 已与angularjs 1.2,最可能会解决你的烦恼。请参阅第靠近负载更多的了解它。如果你把你的
$digest already in progress error 架构,然后是错误的:你不需要redigest或你的范围,你应该不收费(湖是在下面)。
如何组织你的代码
当你把那个错误,你试图消化你的范围,它已经在进步:因为你不知道你在那州范围的点,你需要在批处理与它的消解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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); }); |
如果你知道你所做的工作和在小指令集的一部分应用在大角,你可以选择将美元美元而不是救过消化性能。
angularjs 1.2更新。
一个新的,强大的方法已被添加到任何范围:
这是不是很安静
在案件的所有论文和别人的,你有一个
使用简便的小助手方法保持此过程干燥:
1 2 3 | function safeApply(scope, fn) { (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn); } |
请参阅http://docs.angularjs.org/error/$rootscope:inprog
当您调用
例如,当有一个库从服务器异步提取项目并缓存它们时,可能会发生这种情况。第一次请求某个项时,将异步检索该项,以免阻止代码执行。但是,第二次该项已经在缓存中,因此可以同步检索。
防止此错误的方法是确保调用
解决方案
简而言之,不要这样做:
1 2 3 4 5 6 7 8 9 | ... your controller code... $http.get('some/url', function(data){ $scope.$apply(function(){ $scope.mydate = data.mydata; }); }); ... more of your controller code... |
这样做:
1 2 3 4 5 6 7 8 9 | ... your controller code... $http.get('some/url', function(data){ $timeout(function(){ $scope.mydate = data.mydata; }); }); ... more of your controller code... |
只有当您知道运行的代码总是在角度代码之外运行时,才调用
除非有人意识到使用
我对第三方脚本也有同样的问题,比如codemirror和krpano,即使使用这里提到的安全应用方法也没有为我解决错误。
但是解决这个问题的方法是使用$timeout服务(不要忘记先注入它)。
因此,类似于:
1 2 3 | $timeout(function() { // run my code safely here }) |
如果在代码中使用
this
可能是因为它在工厂指令的控制器中,或者只是需要某种绑定,所以您可以执行如下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | .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(); }] ) |
当你得到这个错误时,它基本上意味着它已经在更新你的视图。您真的不需要在控制器中调用
在最短
1 | $timeout(angular.noop) |
您也可以使用evalasync。它将在摘要完成后运行!
1 2 3 | scope.evalAsync(function(scope){ //use the scope... }); |
首先,不要这样修
1 2 3 | if ( ! $scope.$$phase) { $scope.$apply(); } |
它没有意义,因为$phase只是$digest循环的一个布尔标志,所以有时您的$apply()不会运行。记住这是个坏习惯。
相反,使用EDOCX1[0]
1 2 3 4 5 | $timeout(function(){ // Any code in here will automatically have an $scope.apply() run afterwards $scope.myvar = newValue; // And it just works! }); |
如果使用下划线或lodash,则可以使用defer():
1 2 3 | _.defer(function(){ $scope.$apply(); }); |
如果使用这种方式,有时仍然会出错(https://stackoverflow.com/a/12859093/801426)。
试试这个:
1 2 | if(! $rootScope.$root.$$phase) { ... |
你应该使用美元或美元的evalasync超时根据上下文。
这是一个很好的解释:A链接
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
我建议您使用自定义事件,而不是触发摘要循环。
我发现广播自定义事件并注册此事件的侦听器是触发您希望发生的操作的一个很好的解决方案,无论您是否处于摘要循环中。
通过创建自定义事件,您对代码的效率也会更高,因为您只触发订阅了所述事件的侦听器,而不会像调用scope时那样触发绑定到作用域的所有监视。$apply。
1 2 3 4 5 6 | $scope.$on('customEventName', function (optionalCustomEventArguments) { //TODO: Respond to event }); $scope.$broadcast('customEventName', optionalCustomEventArguments); |
试用使用
1 2 3 | $scope.applyAsync(function() { // your code }); |
而不是
1 2 3 | if(!$scope.$$phase) { //$digest or $apply } |
$applyasync计划稍后调用$apply。这可用于将需要在同一摘要中计算的多个表达式排队。
注意:在$digest中,只有当当前作用域是$rootscope时,$applyasync()才会刷新。这意味着,如果在子作用域上调用$digest,它将不会隐式刷新$applyasync()队列。
Exmaple:
1 2 3 4 5 6 7 8 9 10 11 | $scope.$applyAsync(function () { if (!authService.authenticated) { return; } if (vm.file !== null) { loadService.setState(SignWizardStates.SIGN); } else { loadService.setState(SignWizardStates.UPLOAD_FILE); } }); |
参考文献:
1.AngularJS 1.3中的scope.$applyAsync()与scope.$evalasync()。
Moo在为我们创建可重用的$safeapply函数方面做得很好:
https://github.com/yearofmoo/AngularJS-Scope.SafeApply
用途:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //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); |
而不是使用
在我知道
根据文件,
1 2 3 4 5 6 7 8 9 | function $apply(expr) { try { return $eval(expr); } catch (e) { $exceptionHandler(e); } finally { $root.$digest(); } } |
在我的例子中,一个
通过在watch表达式中用
因此,如果Digest由于角度上的其他变化而以任何方式运行,那么您只需要执行
由于角文件称检查
超时和延迟方法在DOM中创建一个未分析的
每次都有效的唯一方法是:
我不明白这种方法的危险性,也不明白为什么评论和角度团队中的人把它描述成黑客。命令似乎精确易读:
"Do the digest unless one is already happening"
在咖啡里,它甚至更漂亮:
这有什么问题?有没有其他办法不犯规?$safeapply看起来不错,但也使用了
服务:这是我的工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); } }; }); |
这是一个例子:"它是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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']); }; |
我已经使用这个方法似乎工作非常精细。这只是在等待的时间周期已经完成,然后
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } } |
但这类似于上面的答案之前,我将忠实地……服务地址:A
1 2 3 4 5 6 | //sometimes you need to refresh scope, use this to prevent conflict this.applyAsNeeded = function (scope) { if (!scope.$$phase) { scope.$apply(); } }; |
你可以使用
$timeout
预防的错误。
1 2 3 4 5 | $timeout(function () { var scope = angular.element($("#myController")).scope(); scope.myMethod(); scope.$scope(); },1); |
发现这coderwall.com:http:///P/ngisma沃克在弥敦道(近底部页)和一rootscope鱼鳞美元safeapply创建功能,代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; } ]); } ]); |
这将解决你的问题:
1 2 3 | if(!$scope.$$phase) { //TODO } |