我发现自从在angular中构建应用程序以来,我越来越需要手动将页面更新到我的范围。
我知道的唯一方法是从控制器和指令的范围调用$apply()。这样做的问题是,它不断向控制台抛出一个错误,错误如下:
错误:$digest已在进行中
有人知道如何避免这种错误或以不同的方式实现相同的事情吗?
我发现自从在angular中构建应用程序以来,我越来越需要手动将页面更新到我的范围。
我知道的唯一方法是从控制器和指令的范围调用$apply()。这样做的问题是,它不断向控制台抛出一个错误,错误如下:
错误:$digest已在进行中
有人知道如何避免这种错误或以不同的方式实现相同的事情吗?
当前回答
这是我的utils服务:
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']);
};
其他回答
这里的许多答案都包含很好的建议,但也可能导致混淆。简单地使用$timeout既不是最好的解决方案,也不是正确的解决方案。此外,如果您关注性能或可伸缩性,请务必阅读。
你应该知道的事情
$$阶段对框架是私有的,这有充分的理由。$timeout(回调)将等待当前摘要周期(如果有)完成,然后执行回调,然后在结束时运行完整的$apply。$timeout(callback,delay,false)将执行相同的操作(在执行回调之前有一个可选的延迟),但不会触发$apply(第三个参数),如果您没有修改Angular模型($scope),则会节省性能。$范围$apply(callback)调用$rootScope等$digest,这意味着它将重新划分应用程序的根范围及其所有子范围,即使您在一个独立的范围内。$范围$digest()将简单地将其模型同步到视图,但不会消化其父范围,这可以在使用隔离范围(主要来自指令)处理HTML的隔离部分时节省大量性能$摘要不接受回调:执行代码,然后进行摘要。$范围$angularjs 1.2引入了evalAsync(回调),它可能会解决大部分问题。请参考最后一段了解更多信息。如果你得到了$digest已经在进行中的错误,那么你的架构是错误的:要么你不需要重新定义你的范围,要么你不应该负责这个(见下文)。
如何构造代码
当你遇到这个错误时,你正在试图消化你的范围,而它已经在进行中:因为你不知道你的范围在那一点上的状态,你不负责处理它的消化。
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中都添加了一个新的强大方法。基本上,如果发生回调,它将在当前摘要周期内执行回调,否则新的摘要周期将开始执行回调。
这仍然不如$scope$如果你真的知道你只需要同步HTML的一个孤立部分(因为如果没有正在进行的话,会触发一个新的$apply),那么这是最好的解决方案,但是当你执行一个你不知道是否会同步执行的函数时,例如在获取一个可能缓存的资源之后,否则将同步本地获取资源。
在这些情况下,以及所有其他情况下$范围$$阶段,请确保使用$scope$evalAsync(回调)
我对第三方脚本也有同样的问题,比如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();
}]
)
保持此过程干燥的简便小助手方法:
function safeApply(scope, fn) {
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
当我们要求angular运行摘要循环时,问题基本上就要出现了,尽管它正在进行中,这对angular的理解造成了问题。控制台中的结果异常。1.调用范围没有任何意义$$timeout函数中的apply(),因为在内部它也会这样做。2.该代码使用普通JavaScript函数,因为它的本机非角度定义,即setTimeout3.要做到这一点,你可以利用if(!scope.$$phase){范围$evalAsync(函数){});}
如果你这样做,有时你仍然会出错(https://stackoverflow.com/a/12859093/801426).
试试看:
if(! $rootScope.$root.$$phase) {
...