AngularJSでの非同期処理

1/26に開催されたAngularJSハッカソンに参加してきました。

ハッカソンは初めてでしたが、スタッフの方やチームに恵まれエキサイティングな一日でした。中でも新鮮だったのは、Dropbox上に作業エリアを作ると共同作業がはかどることです。今後もお世話になりそうです。

今回はチーム編成の都合上サーバーサイドの担当だったので、目当てのAngular.jsにはほとんど触れませんでした。そのため復習がてら本家のTutorial を通していくつか引っかかった点を調査してまとめました。

Controllerで非同期処理を扱う

以下のコードでは、“Change the world!”と表示されることを期待していたのですが、実際は“Hello world!”と表示されます。どうしてでしょうか?

  • index.html
<div ng-controller="MainCtrl"> 
  <p>{{title}}</p> 
</div>
  • app.js
var MainCtrl = function($scope){ 
  $scope.title = “Hello world!”; 
  setTimeout(function(){ 
    $scope.title = “Change the world!”; // 実行後Viewが更新されない 
  }, 0); 
}

チュートリアル には以下のようにあります。

Once an application is bootstrapped, it will then wait for incoming browser events (such as mouse click, key press or incoming HTTP response) that might change the model.

アプリは起動するとモデルを変更するようなブラウザイベント(クリックや、キー入力、Httpレスポンスなど)を待ちます。

つまりAngularでイベントを発生させないとModelとViewは同期されないようです。そのためController側で非同期処理を走らせてModelを変更したいときはAngularが用意しているモジュールを使いましょう。Ajaxには$httpを、setTimeoutなどの遅延処理には$timeoutなどです。

  • app.js
var MainCtrl = function($scope, $timeout){ 
  $scope.title = “Hello world!”; 
  $timeout(function(){ 
    $scope.title = “Change the world!”; // 実行後Viewが更新される 
  }, 0); 
}

モジュールのインポート動作

AngularはDependency Injection(DI)パターンを採用しているため、インポートではなくinjectと呼ぶのが正確かもしれませんが、分かりやすさのためにここではインポートと言うことにします。

デフォルトでは、モジュールはControllerの引数名にしたがってインポートされます。例えば上記の例を以下のようにしても、同じ変数に同じモジュールがインポートされます。

var MainCtrl = function($timeout, $scope){

逆に$timeoutaなど、不適切な引数名をここに入れるとエラーになります。ここちょっと不思議じゃないですか?変数名を取得するなんてそんなメタなプログラムどうやって書いてるんでしょう?答えは以下のannotate関数にあります。

https://github.com/angular/angular.js/blob/master/src/auto/injector.js

見てびっくりしたんですが、関数名をtoString()してから引数名をパースしてるんですね。よくこんな事思いつくもんだなぁと感心しました。

またClosure Compilerで最適化するときなど、引数名を変更したい場合は以下のようにすればOKです。

MainCtrl.$inject = [’$scope’, ’$timeout’];

その他雑感

従来のViewとModelの分離は、Javascript側にDOMセレクターなどを押し込め、HTML側のJavascriptコードを排除する事に重点が置かれていました。Angularはアプローチが逆で、Javascript側からDOM情報を排除する代わりにHTML側からJavascriptで使う変数名やメソッドなどを指定しています。

これは非常に理にかなっていて、無数のグローバル変数であるDOM要素をJavascript側から指定するよりも、スコープ内に閉じ込められた変数をHTML側で指定する方がシンプルになるのは自明です。反面、Javascript側でガンガンDOM要素を生成するような場合とは相性が悪いように思います。

実際はjQueryなどのライブラリと併用して使う事が想定されているので問題は起こりませんが、jQuery使えるならそれでいいじゃないかと言う声も現場では大きいかもしれません。ともあれこのような野心的な試みには思わずエールを送りたくなります。ソースコードも綺麗ですし今後の進展が楽しみですね。