By

AngularJS $apply()と$digest()について

ビジネスバンクグループエンジニアの 栗山 宗久 です。

弊社で開発しているALLINアプリケーションのフロントではAngularJSを使用しています。 AngularJSを使用することでデータバインドを簡単に行うことができ、クリックなどのイベント処理も容易に行うことができます。 また、AngularJS用のライブラリも多く存在します。

しかし、AngularJSを用いない生のjqueryのライブラリを使用したい時があります。 AngularJSの管轄としていないところでデータを変動させるとViewが思ったタイミングでレンダリングしなかったりします。 では、どうすればいいのでしょうか?

そこで、$applyを使用します。  


$applyを使用し、$digestのサイクルを開始させる

前回の記事でも書きましたが、AngularJSにデータの変更を通知し、 Viewを再描画するには$digestのサイクルを実行しなければなりません。

そのために、scopeに紐づく$digest()を直接実行するということもできますが、親scopeには通達されませんので、あまり使用しません。(独立したscopeを持ったHTMLを使用する場合には利用したりもします。directiveとかです。)

$digest()を直接実行する代わりに、$apply()を実行します。$apply()は内部で$root.$digest()を実行し、rootでdigestのサイクルを実行した後、順々に全ての子scopeのwatchを実行していきます。

次に、$applyの利用方法について見ていきます。


$applyの使用方法

setTimeout()を使用し、sopeのmodelを変更するとします。下記例のように$applyを使用せずscope.messageの値を変更してもViewのmessageは変更されません。

  $scope.message = "Hi";
  $scope.getMessage = function() {
      setTimeout(function () {
          $scope.message = "Hello World!";
          console.log('message:'+$scope.message);
          // AngularJS unaware of update to $scope
      }, 2000);
  }

  $scope.getMessage();

scopeのmodelが変更されたことを通達するために、$applyを使用し、下記のように実装します。

  $scope.message = "Hi";
  $scope.getMessage = function() {
      setTimeout(function () {
          $scope.$apply(function() {
              $scope.message = "Hello World!";
              console.log('message:'+$scope.message);
          });
      }, 2000);
  }

  $scope.getMessage();

これでsetTimeoutで設定した時間が経過するとViewのmessageが意図した表記に変わります。

ここで注意して欲しいことがあります!  
それは、$digestのサイクルの実行中に$applyを実行してはいけないということです。

実行した場合、Error: $digest already in progressというエラーが起きてしまいます。 これを解決するにはどうすればいいでしょうか。

検索すると、下記方法で解決するといいという例が出てきたりします。

  // bad practice!!
  if (!$scope.$$phase) {
      $scope.$apply();
  }

しかし、これはドキュメントにのっているように使用すべきでない、Bad Practiceです。  
これを解決する手段として、$timeoutか$evalAsyncがあります。


$timeout

$timeoutを指定すると、実行している$digestのサイクルが完了した後で$timeoutのコールバックを実行するという仕組みになっています。 $timeoutは内部で$applyを実行するので、エラーを起こさずに$applyを実行することができます。

  $timeout(function(){
      // Any code in here will automatically have an $scope.apply() run afterwards
      $scope.myvar = newValue;
      // And it just works!
  });

$evalAsync

$evalAsyncを指定すると、$evalAsyncの実行は"async queue"に渡され、評価されます。

  $scope.$evalAsync(function(){
      // Any code in here will automatically have an $scope.apply() run afterwards
      $scope.myvar = newValue;
      // And it just works!
  });

$timeoutと$evalAsyncのどちらを使用するべきか?

stackoverflowブログにも書いてありますが、場合を分けて使用するべきです。 動作の違いも詳しく書いてあるので、是非参考にしてください。(色々な意見があるので、どれが正しいかは実装してみてって感じですね)

次回の私の担当記事ではAngularとは違う話をするのでお楽しみに〜。


エンジニア募集中!

ビジネスバンクグループではエンジニアを募集中しています。

弊社が採用しているテクノロジや開発環境に興味を持った方は、 ここから是非エントリー を!