Solution Analysis of AngularJS Error $apply already in progress

  • 2021-07-16 01:08:11
  • OfStack

In this paper, an example is given to analyze the solution of AngularJS error reporting $apply already in progress. Share it for your reference, as follows:

If we use $scope. $apply () or $scope. $digest () in AngularJS, we are likely to encounter an error like the following. Although this error does not have much impact, it still looks very uncomfortable in the log. Exceptions or errors recorded in the log should be problems that need to be paid attention to and solved, otherwise there is no need to appear in the log.


Error: [$rootScope:inprog] $apply already in progress
http://errors.angularjs.org/1.3.13/$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)

The following code will report the above error:


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

This error is easy to understand: the angularJS framework itself is already doing dirty data detection, so we don't need to call $apply or $digest manually. A natural question arises here: When do we need to call $apply or $digest manually, and when don't we need it? This is a good question, and I don't know it now. At present, I can only list two situations that I encountered in my project that require manually calling $apply.

Case 1: If there are asynchronous operations in controller, such as ajax callback, timeout delay, etc. It can be understood that due to asynchronous (delay), when the callback function is started, the dirty value detection in angularJS itself controller has ended, and the data change caused by the callback function cannot be detected.


<html>
 <head>
  <script src="jquery-1.11.1.min.js"></script>
  <script src="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();// Manual dirty value detection is required , Otherwise, the data cannot be refreshed to the interface 
      },1000);
    });
    $(function(){
      angular.bootstrap($("#div1")[0], ["myModule"]);
    })
  </script>
 </head>
 <body>
  <div id="div1" ng-controller="ctrl_1">
    <div>{{text}}</div>
    <input id="btn" type="button" value="jquery-event"></input>
  </div>
 </body>
</html>

If this code does not call $scope. $apply (), the data will not be flushed to the interface.

Case 2: Modify the data in $scope in jQuery code. In this case, the data in $scope is manipulated outside the angular framework, and it is normal for angular not to detect data changes.


<html>
 <head>
  <script src="jquery-1.11.1.min.js"></script>
  <script src="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 id="div1" ng-controller="ctrl_1">
    <div>{{text}}</div>
    <input id="btn" type="button" value="jquery-event"></input>
  </div>
 </body>
</html>

In the event handler of JQuery, we can get its associated $scope object through dom, and then modify the data in $scope. In this case, you must also call $scope. $apply () manually.

That is to say, we must know which cases require manual $apply and which cases do not require manual $apply, which seems simple, but it is not. Look at the code in our project:


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;
      //  Do you want it $scope.$apply()?
          },
          error: function() {
            console.error("invoke service["+optionsJson.serviceId+"] error.");
          }
    });
  }
  $scope.loadListFromService();
});

The function loadData () is very much like the ajax callback, which is true. This API is only encapsulated at one point, and the approximate code is as follows:


function loadData(options)
{
  //  Read from local cache , Soon 
  var dataInCache = U.loadDataFromCache(options.serviceId);
  if(dataInCache)
  {
    options.success(dataInCache);
  }
  else
  {
    // Asynchronous ajax
    U.readDataFromServer(options.serviceId, options.data, function(response){
      options.success(response);
    });
  }
}

Due to the influence of cache, $scope. loadListFromService () becomes less controllable. If there is already a cache locally, it is very fast to read the cache directly. At this time, there is no need to manually $apply;; If this is the first time and there is no local cache, then it becomes case 1, and we need to manually $apply. Obviously, where the loadData () function is called, it is not necessary and should not pay attention to whether there is a cache or not. At this time, it is not so easy to judge whether to manually $apply. In a simple and crude way, call $scope. $apply () manually no matter what, so that the function will not be a problem, but the errors mentioned at the beginning of this article will inevitably appear in the log.

A $$phase variable is provided in $scope in angular. If the value of this variable is "$digest" or "$apply", it means that angular itself is already doing dirty value detection, and we don't need to call $apply or $digest again. Otherwise, we need to call $apply or $digest manually. Using this attribute, we can easily solve the above errors, and judge 1. The following is a tool function, which is easy to understand.


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

Finally, the attributes or methods of $digest, $apply and $phase are all private in $scope, so it is best not to use them. If you use these methods, you can basically assert that there is something wrong with your code, and it is not organized according to angular. For example, setTimeout in Case 1 can be completely replaced by $timeout in angular, which is the recommended way, rather than remedied by $apply.


<html>
 <head>
  <script src="jquery-1.11.1.min.js"></script>
  <script src="angular.js"></script>
  <script>
    var myModule = angular.module('myModule', []);
    myModule.controller("ctrl_1",function($scope, $timeout){
      $scope.text = "place";
      $timeout(function(){
        $scope.text = "value setted after time out";
      },1000);
    });
    $(function(){
      angular.bootstrap($("#div1")[0], ["myModule"]);
    })
  </script>
 </head>
 <body>
  <div id="div1" ng-controller="ctrl_1">
    <div>{{text}}</div>
    <input id="btn" type="button" value="jquery-event"></input>
  </div>
 </body>
</html>

So the best way to solve "$apply already in progress" is not to use $scope. $apply () or $scope. $digest ().

For more readers interested in AngularJS, please check out the topics on this site: "Introduction and Advanced Tutorial of AngularJS" and "Summary of AngularJS MVC Architecture"

I hope this article is helpful to everyone's AngularJS programming.


Related articles: