AnglarJSエラーを報告する$appy already in progressの解決方法分析


本論文の実例はAnglarJSが$appy already in progressを誤って報告した解決方法を分析したものである。皆さんに参考にしてあげます。具体的には以下の通りです。
私たちがAnglarJSの中の$scope.$apply()$scope.$digestを使ったら、私たちは次のようなエラーに出会うかもしれません。このエラーはあまり影響がありませんが、ログに記録されている異常やエラーは、関心と解決が必要です。

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)

次のコードはこのエラーを報告します。

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

このエラーはよく分かります。angglarJSフレーム自体はすでに汚れたデータを検出しています。私たちはもう手動でappyまたはdigestを呼び出す必要がありません。ここで自然に一つの疑問が出てきました。いつ私たちは手動でappyまたはdigestを呼び出す必要がありますか?これはいい問題です。今はよく分かりません。プロジェクトで出会った2つの状況をリストするしかないです。
状況1:controllerに非同期操作がある場合、例えばajaxフィードバック、timeout遅延など。非同期(遅延)の存在により、コールバック関数の実行が開始されると、angglarJS自身のcontrollerにおける汚値検出が終了し、コールバック関数が検出されなくなり、データの変化が生じることが理解できる。

<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();//          ,           
      },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>

このコードが呼び出されていない場合、データは画面に更新されません。
ケース2:jQueryコードの中で$scopeのデータを修正します。この場合は、angglarフレーム外で$scope中のデータを操作しますが、angglarはデータの変化を検出できないのが正常です。

<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>

JQueryのイベントハンドラ関数では、domを介して関連する$scopeオブジェクトを取得し、さらに$scopeのデータを修正することができます。この場合は、手動での呼び出しも必要です。
つまり、私たちはどのような状況がマニュアルで必要なのかを明確にしなければなりません。どのような状況がマニュアルで必要なのかは分かりません。私たちのプロジェクトのコードを見てください。

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.loadListFroomService()はもう制御できなくなりました。もしローカルにキャッシュがあったら、直接にキャッシュを読み込むのはとても速いです。この時はマニュアルが必要ではありません。初めてなら、ローカルにキャッシュがないと状況が変わります。手動で$明らかにloadData()関数を呼び出すところは、キャッシュの存在があるかどうかにも注目すべきではないです。この時はマニュアルではなく、さほど簡単ではないと判断します。簡単で乱暴な方法で、どうしても手動で$scope.$applyを呼び出します。このような機能は問題ないですが、日記には文章の中で最初に述べたエラーが出てくることは避けられません。
この変数の値が「$digest」または「$apply」であれば、angglar自身が既に汚れ値を検出していることを表しています。apperまたはdigestを呼び出す必要はありません。そうでなければ、私たちは手でapplyまたは$digestを呼び出す必要があります。この属性を使えば、上のミスを解決しやすくなります。判断してみてください。次はツール関数です。分かりやすいですよね。

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

最後に、$digest、appy、$phaseのこれらの属性や方法は、実際には$scopeの中のプライベートです。使わないほうがいいです。これらの方法を使えば、基本的にコードに問題があると断言できます。anglar方式によってコードを整理していません。例えば、場合1のsetTimeoutは、完全にanglarの中の$timeoutで代替できます。これが推奨の方式です。

<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>

したがって、「appy already in progress」を解決する一番いい方法は、$scope.$appy()$または$scope.$digest()を使わないことです。
AnglarJSに関する詳細について興味がある読者は、このサイトのテーマを見ることができます。
この記事で皆さんのAnglarJSプログラムの設計に役に立ちますように。