1. ホーム
  2. angularjs

AngularJS がエラー $digest already in progress を報告する

2022-02-19 16:20:37


この問題については、公式サイトに詳細な説明と回避策が掲載されています。https://docs.angularjs.org/error/$rootScope/inprog?p0=$digest.

以下、原因と解決策を簡単に説明します。

ここで、attr.setFocusIfの値をリッスンして、その値が変化したらエレメント要素にフォーカスが当たるようにするディレクティブを定義するとします。

myApp.directive('setFocusIf', function() {
  return {
    link: function($scope, $element, $attr) {
      $scope.$watch($attr.setFocusIf, function(value) {
        if ( value ) { $element[0].focus(); }
      });
    }
  };
});

このディレクティブをhtmlで次のように使用するとします。

<input set-focus-if="hasFocus" ng-focus="msg='has focus'">
<button ng-click="hasFocus = true">Focus</button>





この時点で、ngFocusアクションをトリガーする方法は2つあり、そのうちの1つは次のように実行されます。

(1) 入力フォームをクリックする

(2) 入力フォームがフォーカスされる

(3) ng-focusをトリガーして、新しい$apply()を開始し、msgの値を "has fcous"に設定します。

2つ目の方法は、次のように進みます。

(1) ボタンをクリックする

(2) ng-clickがトリガーされ、新しい$apply()が開始され、hasFcousの値がtrueに設定されます。

(3) $apply()で$digest()を実行し、setFocusIFディレクティブの$watchコールバック関数をトリガーする。

(4) $watch コールバック関数が実行され、入力フォームにフォーカスが当たります。

(5) ng-focusディレクティブが起動し、新しい$apply()が開始されて、msgの値が "has fcous" に設定されます(以下同様)。 ある$digestの中で別の$digestを開始すると問題が発生し、例外がスローされる)

解決方法

 原因が分かれば解決は簡単で、手順(5)でクリーンな環境で新しい$digestを有効にすればよいのです。タイムアウト(fn, 0 , false)を使用します。falseを指定すると、そのfnを$apply()に含めないようにangularjsに指示することがほとんどです。 

修正 ディレクティブのコードは以下のようになります。

myApp.directive('setFocusIf', function($timeout) {
  return {
    link: function($scope, $element, $attr) {
      $scope.$watch($attr.setFocusIf, function(value) {
        if ( value ) {
          $timeout(function() {
            // We must reevaluate the value in case it was changed by a subsequent
            // watch handler in the digest.
            if ( $scope.$eval($attr.setFocusIf) ) {
              $element[0].focus();
            }
          }, 0, false);
        }
      });
    }
  }
});





これは例外なく実行されます