ちょっと話題の記事

AngularJSを使ってみました

2012.06.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

毎度お世話になっております。クラスメソッドの稲毛です。

6月の中旬にGoogle Developers Blogで「AngularJS」という新しいWebテンプレートフレームワークが発表されました。

MVCモデルを採用しているということで、実際にモデルの変更がビューに反映される様を確認できるサンプルを作成してみました。

Twitter Search Sample

今回作成したのは、テキスト入力へ検索ワードを入力しエンターキーを押下すると、TwitterのSearch API(JSONP)でツイートを検索し一覧表示するといったサンプルです。

HTML

1. スクリプトをロードする

AngularJSのスクリプトファイルを読み込みます。

  <script type="text/javascript" src="http://code.angularjs.org/angular-1.0.0.min.js"></script>

2. AngularJSアプリケーションとして宣言する

HTMLファイルがAngularJSによるWebアプリケーションであることを示す為に、html要素の属性として「ng-app」を付与します。この指定が無いとAngularJSによるテンプレート機能は活性化せずHTMLが素のまま表示されます。

<html ng-app>

3. コントローラを指定する

ビューに対するコントローラを「ng-controller」属性で指定します。値にはコントローラとして扱うオブジェクトの関数名(ここではTwitterSearchCtrl)を指定します。今回はひとつのビューしか扱っていないのでbody要素へ記述しています。

<body ng-controller="TwitterSearchCtrl">

今回コントローラを用意するJavaScriptファイルはsearch.jsとして作成します。こちらも予めscriptタグを記述しておきます。

  <script type="text/javascript" src="js/search.js"></script>

4. 入力フォームを用意する

input要素を用意します。入力された値をモデルにバインドする為に「ng-model」属性を利用して対象となるモデルの名称を指定します。ここではモデル「query」とバインドしています。

          <input type="text" class="search-query" placeholder="Search" ng-model="query"/>

エンターキーの押下をハンドリングする為に、form要素にsubmitで実行される関数名(ここではdoSearch)を「ng-submit」属性で指定します。ここで指定したハンドラは後にコントローラで用意します。

        <form class="navbar-search pull-left" ng-submit="doSearch()">

5. テンプレートの繰り返しを利用する

検索結果はJSON形式で取得します。その中に含まれるツイートの配列をリスト状に表示する為に、AngularJSのテンプレート機能の繰り返しを利用します。要素に「ng-repeat」属性を指定することで 、その要素が持つ子要素が繰り返し描画されます。ここでは値として「result in results」と指定していますが、これはモデル「results」に含まれるひとつのデータを「result」という名称で利用して繰り返すということを表しています。

    <div ng-repeat="result in results">

6. バインディングを利用する

モデルをビューへ紐付ける為にはバインディングを利用します。バインディングは二重中括弧で指定します。

        <div class="span2" style="text-align: center;">
          <div><img ng-src="{{result.profile_image_url}}"/></div>
          <div>{{result.from_user}}</div>
        </div>
        <div class="span10">
          <div>{{result.text}}</div>
        </div>

二重中括弧の中にはモデル名を指定します。先の「ng-repeat」によってモデル「results」の単一オブジェクトは「result」と定義されているので、resultオブジェクトのプロパティを参照してバインディングを行います。ここでは「profile_image_url」「from_user」「text」を利用しています。

img要素は、src属性の値としてバインドの記述を行うとその値でリクエストが行われエラーとなってしまいます。

          <div><img src="{{result.profile_image_url}}"/></div><!-- NG! -->

その対策として、AngularJSにより提供される「ng-src」属性を利用して動的にsrc属性が挿入されるように記述します。

          <div><img ng-src="{{result.profile_image_url}}"/></div>

以上でHTMLファイルは完成です。

search.html

<!DOCTYPE html>
<html ng-app>
<head>
  <meta charset="utf-8">
  <title>Twitter Search Sample</title>
  <link rel="stylesheet" href="css/bootstrap.css">
  <link rel="stylesheet" href="css/bootstrap-responsive.css">
  <script type="text/javascript" src="http://code.angularjs.org/angular-1.0.0.min.js"></script>
  <script type="text/javascript" src="js/search.js"></script>
</head>
<body ng-controller="TwitterSearchCtrl">
  <div class="navbar">
    <div class="navbar-inner">
      <div class="fluid-container">
        <a class="brand" href="#">Twitter Search Sample</a>
        <form class="navbar-search pull-left" ng-submit="doSearch()">
          <input type="text" class="search-query" placeholder="Search" ng-model="query"/>
        </form>
      </div>
    </div>
  </div>
  <div class="container">
    <div ng-repeat="result in results">
      <div class="row">
        <div class="span2" style="text-align: center;">
          <div><img ng-src="{{result.profile_image_url}}"/></div>
          <div>{{result.from_user}}</div>
        </div>
        <div class="span10">
          <div>{{result.text}}</div>
        </div>
      </div>
      <hr/>
    </div>
  </div>
</body>
</html>

JavaScript

ビュー(HTML)で定義したコントローラをJavaScriptファイルに記述していきます。

1. コントローラへハンドラを用意する

ビューのform要素に定義したハンドラ「doSearch」は、コントローラ「TwitterSearchCtrl」の引数「$scope」の関数として記述します。

function TwitterSearchCtrl($scope, $http) {
  $scope.doSearch = function() {

他にも「$http」とありますが、これはAngularJSが提供するサービスのひとつで、ブラウザが持つ「XMLHttpRequest」や「JSONP」を利用する際に用意します。これらサービスの中身はAngularJSによってDI(依存性の注入)が行われる為、コントローラの引数に記述するだけで利用することができます。

2. モデルを参照する

ビューに用意した入力フォームのinputはモデル「query」にバインドしている為「$scope.query」で、入力された文字列を参照することができます。

      + encodeURIComponent($scope.query)

モデルはすべて「$scope」内に存在します。

3. JSONP形式のリクエストを行う

JSONPでのリクエストには先に出てきた「$http」サービスの「jsonp()」を利用します。続けて「success()」にリザルト関数を指定し、引数にセットされるJSONオブジェクトを処理します。

    $http.jsonp(uri).success(function(data) {

JSONP形式ではコールバック関数の名称を指定する必要があります。AngularJSの「$http.jsonp」サービスを利用する場合は必ず「JSON_CALLBACK」を指定します。

      + '&callback=JSON_CALLBACK';

4. モデルへオブジェクトをセットする

得られた検索結果データをモデルへセットします。ビューで検索結果のツイート群を「results」と指定しているので、「$scope.results」へツイートの配列をセットします。(Twitter Search APIで得られるJSONオブジェクトは、resultsにツイートの配列が格納されています。)

以上でJavaScriptファイルも完成です。

search.js

function TwitterSearchCtrl($http, $scope) {
  $scope.doSearch = function() {
    var uri ='http://search.twitter.com/search.json?q='
      + encodeURIComponent($scope.query)
      + '&callback=JSON_CALLBACK';
    $http.jsonp(uri).success(function(data) {
      $scope.results = data.results;
    });
  };
}

ソースコード

実行結果

上部のナビゲーションバーにあるテキスト入力欄に検索ワードを入力しエンターキーを押下すると、Twitter Search APIを利用した検索結果が一覧として表示されます。

検索ワードを変更して再検索をすれば、きちんとそのワードに対する検索結果で描画が更新されます。コントローラ内での実装は検索結果をモデルへ代入しているだけなので、モデルの変更がAngularJSによってビューに反映されていることがお分かり頂けると思います。

まとめ

AngularJS独自の要素(「ng-app」や「ng-controller="Controller"」など)を利用している為にW3Cのマークアップ検証ではエラーとなってしまいますが、検証に通る必要がある場合にはクラス属性の値として記述(「class="ng-app"」や「class="ng-controller: Controller"」など)することも可能となっています。抜かりの無い作りは流石だなと思いました。(^^)
モデル、ビュー、コントローラの扱いも非常にシンプルで、初めてMVCモデルに触れる方でも習得し易いのではと思います。

AngularJSは他にも様々な機能を持っているようなので今後も注目していこうと思います。