AngularJS の binding がこうだったらいいのになという動きをしてくれた話

ngRepeat を使った以下のようなテンプレートがあるとします。
tweet の内容、Like かどうか、Like ボタンをテーブルで表示します。

<tr ng-repeat="tweet in tweets">
  <td>{{tweet.content}}</td>
  <td>{{tweet.is_liked}}</td>
  <td><button ng-click="like(tweet)">Like</td>
</tr>

コントローラでは API を使って tweet を取得する部分と、ngClick のイベントハンドラを実装します。

$http.get("some_api").success(function(data) {
  $scope.tweets = data;
});

$scope.like = function(tweet) {
  tweet.is_like = true;
};

これで、ある tweet の Like ボタンを押すと、その tweet の [Like かどうか] というビューだけが更新されました。

当たり前の話なんだろうけど、なんとなく ngModel ディレクティブでバインディングしたり、コントローラでインスタンス化したモデル(今回の例で言うと $scope.tweets)が更新された時のみ対応するビューがリフレッシュされるものだと思っていたので、ngRepeat で作ったビューの細かな箇所がピンポイントでリフレッシュされたことに驚いたのでした。

audio 要素で音声再生しようとしたらエラー

<audio ng-src="{{some_object.url}}"></audio>

audio 要素に ng-src ディレクティブでソース指定して、他サーバにある音声を再生しようとしたら

Blocked loading resource from url not allowed by $sceDelegate policy.

というエラーが発生した。

ググったら AngularJS 公式がヒットした。
http://docs.angularjs.org/error/$sce:insecurl

Strict Contextual Escaping (SCE) モードがリソースのロードをブロックしたと。
SCE モードはデフォルトでオンになっているそう。

ドメイン or 他プロトコロのリソースを読み込むには、URL を $sce.trustAsResourceUrl() でラップしてやればいい。

<audio ng-src="{{trustSrc(some_object.url)}}"></audio>
$scope.trustSrc = function(src) {
  return $sce.trustAsResourceUrl(src);
};

Step-7

マルチビューを持つアプリケーションの作り方。

マルチビューとルーティングとレイアウトテンプレート

これまでは1つのビューしかなかったので全てのテンプレートを index.html に記述していた。
マルチビューにするにはまず index.html をレイアウトテンプレートと呼ぼれるものに変える。
そして、カレントルートに従って、部分テンプレート(→これが各ビューのテンプレート)がレイアウトテンプレートの中に入るようにする。

App モジュール

var phonecatApp = angular.module('phonecatApp', [
  'ngRoute',
  'phonecatControllers'
]);

アプリケーション用のモジュールを作る。
ここでは 'phonecatApp' と名付ける。
第2引数ではアプリケーションが依存しているモジュールのリストを与える。ここでは 'ngRoute' と 'phoneControllers' 。

$routeProvider.when API

phonecatApp.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html',
        controller: 'PhoneListCtrl'
      }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html',
        controller: 'PhoneDetailCtrl'
      }).
      otherwise({
        redirectTo: '/phones'
      });
  }]);
  • URL のハッシュフラグメントが '/phones' の場合、phone list ビューが表示される。このビューを作るために phone-list.html テンプレートと PhoneListCtrl コントローラを使う。
  • URL のハッシュフラグメントが '/phones/:phoneId' の場合、phone detail ビューが表示される。このビューを作るために phone-detail.html テンプレートと PhoneDetailCtrl コントローラを使う。

新しく作ったモジュールを使ってアプリケーションを起動するため、ngApp ディレクティブにモジュール名('phonecatApp')を指定しておく。

<!doctype html>
<html lang="en" ng-app="phonecatApp">
...

Controllers

phonecatControllers モジュールを作る。
小さいアプリケーションでは2~3個のコントローラであれば1つのモジュールにまとめてしまう。

var phonecatControllers = angular.module('phonecatControllers', []);
 
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
  function ($scope, $http) {
    $http.get('phones/phones.json').success(function(data) {
      $scope.phones = data;
    });
 
    $scope.orderProp = 'age';
  }]);
 
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
  function($scope, $routeParams) {
    $scope.phoneId = $routeParams.phoneId;
  }]);

Template

$route サービスは ngView ディレクティブと一緒に使われる。
ngView ディレクティブの役割は、カレントルートに合わせてレイアウトテンプレートにビューテンプレートを読み込ませること。

AngularJS Tutorial をやってみた(Step-0 -> Step-2)

http://docs.angularjs.org/tutorial

Step-0

<html ng-app>

ngApp ディレクティブを表す。
上の例だと html 要素がアプリケーションのルート要素と見なされる。
html ページ全体をアプリケーションとすることもできるし、ページの一部をアプリケーションとすることもできる。

<script src="lib/angular/angular.js">

angular.js をダウンロードし、コールバックを登録する。
このコールバックは html ページのロード完了時に実行される。
コールバックは ngApp ディレクティブを探してアプリケーションをブートする。

Nothing here {{'yet' + '!'}}

{{ }} はバインディングを表す。
式を評価した結果を挿入してくれる。
1回きりではなく、式の評価値が変わるたびに更新される。

Step-1

2種類の携帯電話についての情報が書かれたページがある。
ここに静的な HTML を追加してみる。

<p>Total number of phones: 2</p>

Step-2 では AngularJS を使って同じことを動的に実現する方法を学ぶ。

Step-2

Angular アプリケーションでは Model-View-Controller デザインパターンを推奨している。

ビューとテンプレート

ビューとは HTML テンプレートによるモデルの射影
モデルが変わるたびに Angular が適切なバインディング箇所をリフレッシュしてくれることで、ビューが更新される。

  <ul>
    <li ng-repeat="phone in phones">
      {{phone.name}}
      <p>{{phone.snippet}}</p>
    </li>
  </ul>

ハードコーディングされていた電話リストを ngRepeat ディレクティブと2つの Angular 式 {{phone.name}} と {{phone.snippet}} で置き換えた。
<li> タグの中の ng-repeat="phone in phones" は Angular Repeater。
Repeater -> Angular「<li> タグをテンプレートとして、リストの全ての phone に対して <li> 要素を生成して!」

モデルとコントローラ

データモデル(シンプルな配列)は PhoneListCtrl コントローラの中でインスタンス化されている。
コントローラは $scope という引数を取るシンプルなコンストラクタ。

var phonecatApp = angular.module('phonecatApp', []);
 
phonecatApp.controller('PhoneListCtrl', function ($scope) {
  $scope.phones = [
    {'name': 'Nexus S',
     'snippet': 'Fast just got faster with Nexus S.'},
    {'name': 'Motorola XOOM&#8482; with Wi-Fi',
     'snippet': 'The Next, Next Generation tablet.'},
    {'name': 'MOTOROLA XOOM&#8482;',
     'snippet': 'The Next, Next Generation tablet.'}
  ];
});

PhoneListCtrl コントローラを宣言して、AngularJS モジュールに登録した。
ng-app ディレクティブは phonecatApp モジュールを指定していたが、これは Angular アプリケーションをブートした時にロードするモジュール。

コントローラはデータモデルにコンテキストを提供することで、モデルとビューとのデータバインディングを可能にする。
HTML 中の ngController ディレクティブは JavaScript 中の PhoneListCtrl を参照していた。
PhoneListCtrl は phone データを $scope にアタッチする。$scope は root スコープの子孫で、root スコープはアプリケーションが定義された時に作られる。$scope は タグの中にあるすべてのバインディングを利用できる。

スコープ

Angular のスコープの概念は重要。スコープはテンプレートとモデルとコントローラが協働するための接着剤のようなもの。
Angular はスコープとデータモデルとコントローラを使って、モデルとビューを切り離しつつ同期している。
モデルへの変更はビューに反映されるし、ビューへの変更もまたモデルに反映される。

テスト

Angular デベロッパーはテストを書くとき Jasmine's Behavior-driven Development (BDD) フレームワークシンタックスを好む。
http://pivotal.github.io/jasmine/

angular-seed プロジェクトは全てのユニットテストを Karma を使って実行するように設計されている。
http://karma-runner.github.io/0.10/index.html

Jasmine も Karma も初めて聞いた。
テストを実行したら Chrome が起動してテスト結果が表示された。
ソースコードやテストスクリプトを書き換えたら自動で再実行してくれた。
なんて便利!

step-3 ではテキスト検索を学ぶ。


思ったこと

新しいアプリケーションを作りたいと思ったら、angular-seed プロジェクトをもらってきてそこから構築すればよいとのこと。
angular-seed プロジェクトがひな形を提供してくれる。
MVC パターンで開発することを想定しているので、見通しのよい開発ができそう。
テンプレートが見やすく、デザイナさんが見ても何が起こるのか何となく理解できそう。

まとめてみる

Model
データ。コントローラの中(のスコープの中)でインスタンス化される。
View
テンプレートをバインディングやら何やら(ng-repeat とか)によって実体化したもの。
Controller
モデルとビューをくっつける仲介人。スコープを持つ。

AngularJS 入門

なんで AngularJS?

http://angularjs.org/ より。
HTML は静的なドキュメントを記述するのには向いているが、動的なウェブアプリケーションを作るのには向いていない。
AngularJS はアプリケーションを作るのに必要な語彙を HTML に追加する。
→「HTML の語彙を拡張する」っていうところが jQuery とは違う感じがする。jQuery はあくまでも JavaScript のライブラリっていう印象だった。

jQuery v.s. AngularJS

同じことを jQuery と AngularJS とでやって比較してみる。
テキストボックスに名前を入力すると「Hello ○○○○!」って表示される HTML。

jQuery

<!doctype html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script type="text/javascript">
      $(function() {
          var name = $("#name");
          var greeting = $("#greeting");

          name.keyup(function() {
              greeting.text("Hello " + name.val() + "!");
          })
      })
    </script>
  </head>
  <body>
    <input id="name" type="text">
    <h2 id="greeting">Hello John!</h2>
  </body>
</html>

AngularJS

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.8/angular.min.js"></script>
  </head>
  <body>
    <input type="text" ng-model="name">
    <h2>Hello {{name}}!</h2>
  </body>
</html>

AngularJS の方がずいぶんすっきりしている。
jQuery では「ドキュメントの準備が完了したら、処理の対象となる要素を探して、イベントリスナーをセットして…」と詳細な手続きを記述しなければならず、ネストが深くなることもしばしば。
AngularJS ではまず基本となる HTML を記述して、そこにデータバインディングについての記述を少しだけ足せば良い。
公式サイトでも言っていたけれど、一番重要な点は見た目が純粋な HTML と殆ど変わらないということ。
シンプルで、何が行われているのか理解しやすい。

思ったこと

「Angular = 痩せこけた」なるほど。
まだここまでしか見ていないけれど、とても便利な印象。
よく使う手続きをシンプルにまとめるのがライブラリだけれど、JavaScript 全く書かずに動的なウェブアプリケーションを作れるのはすごい。
中のコードもしっかり読みたいと思った。
他にどんなことができるのかとても気になりました。
引き続き勉強します。

年賀状 2.0

新年あけましておめでとうございます!
近年年賀状を書くことはほとんどなかったのですが、古式ゆかしい友人が年賀状を書くと言い出したことから、数人の間で年賀状を送りあう「流れ」になったので、今年は僕も年賀状を書いてみました。年明けてから書きました。ごめんなさい。
急きょ書くことになったので馬にちなんだ絵のアイディアが浮かばず、また数人の友人がプロのデザイナーということもあり、絵で勝負することを恐れ、プログラマらしい年賀状にしてみました。
以前下の記事を読んで、Twilio という電話 API に興味を持っていたので、年始のゆるやかな時間を使って簡単なアプリケーションを作ってみました。
はぁはぁブログ 「ご結婚おめでとう」親友に贈ったコードとデザインの話

Twilio ってなに?

電話を操作する Web API です。Twilio に登録すると Twilio 用の電話番号がもらえます。
この電話番号を使って、着信を受けてあいさつを読み上げたり、MP3 を再生したり、他の人に通話を接続したりすることができます。

Twilio のしくみ

Twilio 用電話番号に着信 -> あらかじめ指定した URL に HTTP リクエスト -> HTTP リクエストに対して制御を記述した XML(TwiML)を返す -> 電話操作

作ったもの

Twilio で電話を受けて、新年の挨拶を録音した MP3 を再生します。
MP3 は11人分用意して、発信者の番号によって再生するファイルを変えました。
昔雑誌「小学1年生」などの企画で、○○レンジャーとおはなしできる電話がありましたが、あんな感じですね。

作り方

Twilio では TwiML と呼ばれる XML で制御を記述します。

<?php
// 電話番号 => 名前の連想配列
$people = array(
	"+81XXXXXXXXX0" => "Foo",
	"+81XXXXXXXXX1" => "Bar",
	"+81XXXXXXXXX2" => "Baz",
	"+81XXXXXXXXX3" => "Qux"
);

// 発信者の番号 => 名前
$name = $people[ $_REQUEST[ "From" ] ];

// 知らない番号ならばデフォルトメッセージを流す
if ( !$name )
{
	$name = "Default";
}

header( "content-type: text/xml" );
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
?>
<Response>
	<!-- 友人ごとの MP3 を再生する -->
	<Play>snd/<?php echo $name ?>.mp3</Play>
</Response>

上記の XML を任意のサーバに置いておきます。
Twilio の設定画面から Twilio 用電話番号に着信があったときに HTTP リクエストを実行する URL を指定できるので、XML の URL を指定しましょう。

あとは年賀状に Twilio 用電話番号を記述しておけば OK です。

AWS 入門

年賀状 2.0
上の記事で Twilio を使った年賀状を作った時に、サーバとして AWS の EC2 インスタンスを使用しました。
AWS のアカウント取得からやってみたのでメモ。

AWS のアカウント取得

  • AWS を使用するには AWS のアカウントが必要
  • Amazon.com のアカウントを AWS のアカウントとして使用することができる
  • Amazon.com のアカウントと Amazon.co.jp のアカウントとは違う

AWS の主な無料利用枠

AWS のアカウントを取得すると、いくつかの無料利用枠が与えられます。
AWS 無料利用枠
これらの無料利用枠は、AWS の新規顧客のみが対象であり、AWS にサインアップした日から 12 か月間利用できる。無料利用の有効期限が切れた場合、またはアプリケーションの使用量が無料利用枠を超えた場合は、標準の料金、つまり従量課金制で支払う必要がある。

EC2 インスタンスの作成

わずか5分!? AWSのEC2でクラウドなウェブサーバーを構築してみた

LAMP ウェブサーバのインストール

Linux, Apache, MySQL, PHP/Perl/Python をインストール。
awsdocumentation

感想

アカウントの取得から phpinfo() の実行まで、調べながらやって30分くらいで完了しました。
今や無料かつ簡単にこんな環境が手に入るなんて、ウェブアプリケーションを作らない理由がないですね!