AngularJS报错$apply already in progress的解决方法分析

如果我们使用了AngularJS中的$scope.$apply()或者$scope.$digest(),我们很可能会遇到类似下面的错误,虽然这个错误没有太大影响,但是在日志中看起来还是很不爽的,日志中记录的异常或者错误,就应该是需要关注和解决的问题,否则就没有必要出现在日志中了。

Error: [$rootScope:inprog] $apply already in progress $rootScope/inprog?p0=%24apply at angular.js:63 at beginPhase (angular.js:14755) at Scope.$apply (angular.js:14499) at new <anonymous> (1%20-%20%E5%89%AF%E6%9C%AC.html:10) at Object.invoke (angular.js:4185) at extend.instance (angular.js:8454) at angular.js:7700 at forEach (angular.js:331) at nodeLinkFn (angular.js:7699) at compositeLinkFn (angular.js:7078)

下面这段代码就会报上面这个错误:

var myModule = angular.module('myModule', []); myModule.controller("ctrl_1",function($scope){ $scope.value = "aty"; $scope.$apply(); //$scope.$digest(); });

这个错误其实很好理解:angularJS框架本身已经在做脏数据检测了,我们没有必要再手动调用$apply或者$digest。这里自然而然出现了一个疑问:什么时候需要我们手动调用$apply或者$digest,什么时候不需要呢?这是个好问题,我现在也不知道,目前我只能列出自己在项目中遇到的2种需要手动调用$apply的情况。

情况1:controller中如果有异步操作,比如ajax回调,timeout延时等。可以这么理解:由于异步(延迟)的存在,当开始执行回调函数的时候,angularJS自身controller中的脏值检测已经结束,无法检测到回调函数导致数据的变化。

<html> <head> <script src="https://www.jb51.net/jquery-1.11.1.min.js"></script> <script src="https://www.jb51.net/angular.js"></script> <script> var myModule = angular.module('myModule', []); myModule.controller("ctrl_1",function($scope){ $scope.text = "place"; setTimeout(function(){ $scope.text = "value setted after time out"; $scope.$apply();//必需手动进行脏值检测,否则数据无法刷新到界面 },1000); }); $(function(){ angular.bootstrap($("#div1")[0], ["myModule"]); }) </script> </head> <body> <div ng-controller="ctrl_1"> <div>{{text}}</div> <input type="button" value="jquery-event"></input> </div> </body> </html>

这段代码如果没有调用$scope.$apply()的话,数据是不会刷新到界面上的。

情况2:在jQuery代码中修改$scope中的数据。这种情况是在angular框架之外操作$scope中的数据,angular不能检测到数据变化是正常的。

<html> <head> <script src="https://www.jb51.net/jquery-1.11.1.min.js"></script> <script src="https://www.jb51.net/angular.js"></script> <script> var myModule = angular.module('myModule', []); myModule.controller("ctrl_1",function($scope){ $scope.text = "place"; }); $(function(){ angular.bootstrap($("#div1")[0], ["myModule"]); $("#btn").click(function(){ var $scope = $("#btn").scope(); $scope.text = "value setted in jquery"; $scope.$apply(); }); }) </script> </head> <body> <div ng-controller="ctrl_1"> <div>{{text}}</div> <input type="button" value="jquery-event"></input> </div> </body> </html>

在JQuery的事件处理函数中,我们能够通过dom拿到其关联的$scope对象,进而修改$scope中的数据。这种情况下,也必需要手动调用$scope.$apply()。

也就是说我们必须要清楚哪儿些情况需要手动$apply,哪儿些情况不需要手动$apply,这看起来很简单,实际上并不是这样的。看一段我们项目中的代码:

var myModule = angular.module('myModule', []); myModule.controller("ctrl_1",function($scope){ $scope.listItems = []; $scope.loadListFromService = function(){ Spl.MessageProcessor.loadData({ serviceId : "url", data : {}, success : function(json) { $scope.listItems = json.results; // 要不要$scope.$apply()? }, error: function() { console.error("invoke service["+optionsJson.serviceId+"] error."); } }); } $scope.loadListFromService(); });

loadData()这个函数很像ajax回调,确实是这样的,这个API不过是做了一点封装,大致代码如下:

function loadData(options) { // 从本地缓存中读取,很快 var dataInCache = U.loadDataFromCache(options.serviceId); if(dataInCache) { options.success(dataInCache); } else { //异步ajax U.readDataFromServer(options.serviceId, options.data, function(response){ options.success(response); }); } }

由于缓存的影响,$scope.loadListFromService()变得不再那么可控,如果本地已经有缓存了,那直接读取缓存是很快的,这个时候不需要手动$apply;如果是第一次,本地没有缓存,那么就变成了情况1,我们需要手动$apply。显然调用loadData()函数的地方,不需要也不应该关注到底有没有缓存的存在,这个时候判断需要不要手动$apply就没有那么容易的。简单粗暴的方式,不管怎么样都手动调用下$scope.$apply(),这样功能不会有问题,但是日志中就不可避免要出现文中开头提到的错误。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwgspf.html