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とは違う話をするのでお楽しみに〜。
エンジニア募集中!
ビジネスバンクグループではエンジニアを募集中しています。
弊社が採用しているテクノロジや開発環境に興味を持った方は、 ここから是非エントリー を!