こんにちは。
膝が痛み出し、冬の足音が聞こえてきました。
正月は実家である北海道に帰省する予定ですが、
あの寒さにやられてしまうのではないかと内心穏やかではありません。
どうも、高橋です。
さて今回はAngularJSを使ってサンプルアプリを作ってみたので
それを記事にしたいと思います。
AngularJSとは
AngularJSとはGoogleが主体となって開発されているJavaScriptフレームワークです。
Knockout.jsとかBackbone.jsとかと比較されるやつです。
Knockout.jsについては当ブログでもいくつか触れている記事がありますので参考にしてください。
今回作るサンプルアプリの要件
- メモを追加/編集/削除できる
- メモには「内容」、「作成日付」を持つ
- メモを一覧表示できる
- メモを内容で検索できる
- メモを作成日付の昇順、降順でソートできる
※今回はサーバサイドで保存はしません
画面はこんな感じです。
追加フォームは普段は隠れていて、追加ボタンを押されたときや編集ボタンを押されたときに表示される想定です。
素のHTMLはこちら。
STEP1 ng-repeatで一覧表示
まずはメモの一覧がHTMLベタ打ちのところをng-repeatというものを使って配列をデータソースとして書き換えてみます。
ng-repeatはこんな感じでつかいます。
1 2 3 |
<ul> <li ng-repeat="value in ['one', 'two', 'three']"><p ng-bind="value"></p></li> </ul> |
さらにng-bindでタグのテキストコンテンツを置換することができます。
ng-bindは{{式}}でも書き換えることができます
1 2 3 |
<p ng-bind="式"></p> ↓ <p>{{式}}</p> |
サンプルソースは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<!doctype html> <html ng-app> <!-- ① --> <head> <meta charset="utf-8"> <link rel="stylesheet" href="angularjs_sample.css"> <script src="http://code.angularjs.org/1.2.1/angular.min.js"></script> <!-- ② --> <script type="text/javascript"> function MemoCtrl($scope) { // ③ //メモの配列データ $scope.memoList = [ {content: "一つ目のメモです", createdAt: (new Date()).toLocaleString()}, {content: "二つ目のメモです", createdAt: (new Date()).toLocaleString()} ]; } </script> </head> <body> <div class="memoBox" ng-controller="MemoCtrl"> <!-- ④ --> <div class="header"> <h1>メモ</h1> <p class="addButton"><button>追加</button></p> </div> <div class="input"> <p><textarea></textarea></p> <p> <button>キャンセル</button> <button>作成</button> </p> </div> <div class="list"> <div class="listOpBox"> <p class="search"><input type="text" name="search" placeholder="検索"></p> <p class="sort"> <button>新しい順</button> <button>古い順</button> </p> </div> <ul class="record"> <li ng-repeat="memo in memoList"> <!-- ⑤ --> <p class="content" ng-bind="memo.content"></p> <p class="op">{{memo.createdAt}} <a href="#">編集</a> <a href="#">削除</a></p> </li> </ul> </div> </div> </body> </html> |
①AngularJSを使うルートのタグに設置します。
②http://angularjs.org/ からAngularJSをダウンロードして<script>タグで読み込みます。
③④MVCのCに当たるng-controllerディレクティブを指定します。今回はあまり深く考えず”
$scope.memoListを定義しておくとng-controller配下では[memoList]でアクセスできる”程度に抑えてもらえれば大丈夫です。
⑤ng-repeatディレクティブで③でセットした配列を繰り返し処理します。
動作するサンプルはこちら
STEP2 ng-showでフォームの表示/非表示を切り替え
次にng-showを使って要素の表示/非表示の切り替えを実装してみます。
ng-showはng-show=”式”の式がtrueならば表示、falseならば非表示というように動作します。
このような感じで使えます。
1 2 3 |
<label><input type="radio" name="onoff" value="1" ng-model="onoffModel" />表示</label> <label><input type="radio" name="onoff" value="0" ng-model="onoffModel" />非表示</label> <p ng-show="onoffModel">コンテンツ</p> |
ソースは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<!doctype html> <html ng-app> <head> <meta charset="utf-8"> <link rel="stylesheet" href="angularjs_sample.css"> <script src="http://code.angularjs.org/1.2.1/angular.min.js"></script> <script type="text/javascript"> function MemoCtrl($scope) { //メモの配列データ $scope.memoList = [{content: "一つ目のメモです", createdAt: (new Date()).toLocaleString()}, {content: "二つ目のメモです", createdAt: (new Date()).toLocaleString()}]; //フォームを表示するかしないかのフラグ $scope.showForm = false; // ① //追加ボタンがクリックされたときの挙動 $scope.startAdd = function () { // ② $scope.showForm = true; }; } </script> </head> <body> <div class="memoBox" ng-controller="MemoCtrl"> <div class="header"> <h1>メモ</h1> <p class="addButton"> <button ng-click="startAdd()">追加</button> <!-- ③ --> </p> </div> <div class="input" ng-show="showForm"> <!-- ④ --> <p> <textarea></textarea> </p> <p> <button ng-click="showForm = false">キャンセル</button> <!-- ⑤ --> <button>作成</button> </p> </div> <div class="list"> <div class="listOpBox"> <p class="search"><input type="text" name="search" placeholder="検索"></p> <p class="sort"> <button>新しい順</button> <button>古い順</button> </p> </div> <ul class="record"> <li ng-repeat="memo in memoList"> <p class="content" ng-bind="memo.content"></p> <p class="op">{{memo.createdAt}} <a href="#">編集</a> <a href="#">削除</a></p> </li> </ul> </div> </div> </body> </html> |
①④ 今回はshowFormという変数を用意しその値によってフォームが表示・非表示されるようにしました。
②③ 追加ボタンをクリックされたときの挙動を定義しています。追加ボタンを押されたらshowFormをtrueにセットするようにしています。
⑤ キャンセルボタンはng-clickの中に直接showFormをfalseにする処理を記述しています。
動作するサンプルはこちら
「追加」ボタンをクリックするとフォームが表示され、「キャンセル」をクリックするとフォームが閉じるようになっています。
STEP3 ng-modelでフォームの内容を取得し、追加機能を実装
次にフォームからデータソースである配列に追加する動きを実装してみます。
ng-modelは<input>や<textarea>などの入力系のタグにセットするとその入力値をJavaScriptで扱えるようになります。
簡単に言うとng-bindの逆というイメージです。
1 2 |
<input ng-model="testModel" /> testModel = <span ng-bind="testModel"></span> |
のように記述するとテキストボックスに入力した値がリアルタイムに<span>に出力されます。
実装はこんな感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<!doctype html> <html ng-app> <head> <meta charset="utf-8"> <link rel="stylesheet" href="angularjs_sample.css"> <script src="http://code.angularjs.org/1.2.1/angular.min.js"></script> <script type="text/javascript"> function MemoCtrl($scope) { //メモの配列データ $scope.memoList = [{content: "一つ目のメモです", createdAt: (new Date()).toLocaleString()}, {content: "二つ目のメモです", createdAt: (new Date()).toLocaleString()}]; //フォームを表示するかしないかのフラグ $scope.showForm = false; //入力中のデータ $scope.inputtingMemo = null; // ① //追加ボタンがクリックされたときの挙動 $scope.startAdd = function () { $scope.showForm = true; }; //作成処理 $scope.addMemo = function () { // ② $scope.inputtingMemo.createdAt = (new Date()).toLocaleString(); //作成日付を入れる $scope.memoList.push(angular.copy($scope.inputtingMemo)); //データソース配列に追加 $scope.inputtingMemo = null; //フォームを初期化 $scope.showForm = 0; //フォームを閉じる }; } </script> </head> <body> <div class="memoBox" ng-controller="MemoCtrl"> <div class="header"> <h1>メモ</h1> <p class="addButton"> <button ng-click="startAdd()">追加</button> </p> </div> <div class="input" ng-show="showForm"> <form name="inputForm"> <p> <textarea name="content" ng-model="inputtingMemo.content"></textarea> <!-- ③ --> </p> <p> <button ng-click="showForm = false">キャンセル</button> <button ng-click="addMemo()">作成</button> </p> </form> </div> <div class="list"> <div class="listOpBox"> <p class="search"><input type="text" name="search" placeholder="検索"></p> <p class="sort"> <button>新しい順</button> <button>古い順</button> </p> </div> <ul class="record"> <li ng-repeat="memo in memoList"> <p class="content" ng-bind="memo.content"></p> <p class="op">{{memo.createdAt}} <a href="#">編集</a> <a href="#">削除</a></p> </li> </ul> </div> </div> </body> </html> |
①③ ng-modelでinputtingMemo.contentを指定します。$scope.inputtingMemo = nullの宣言はなくても動きますが、あった方がプログラムの見通しがよくなると思います。
② フォームで入力した値をデータソースに追加しています。ディープコピーの関数angular.copy()が用意されているのでこちらを使うと便利です。
動作するサンプルはこちら
「追加」ボタンで表示されたフォームからメモが追加可能になっています。
STEP4 フォームのバリデート
簡易的にバリデーションを実装してみます。
<input>などにng-maxlength=”n”と設定すると
フォーム名.インプット名.$error.maxlength
にバリデートされた結果が得られます。
ng-showと組み合わせるとこのように簡易的にバリデーションが実装できます。
1 2 3 4 5 6 |
<form name="testForm"> 5文字以内はOK,6文字以上はNG<br /> <input name="testInput" ng-model="testModel" ng-maxlength="5" /> <span ng-show="!testForm.testInput.$error.maxlength">OK</span> <span ng-show="testForm.testInput.$error.maxlength" style="color: red;">NG</span> </form> |
実装したソースはこちら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<!doctype html> <html ng-app> <head> <meta charset="utf-8"> <link rel="stylesheet" href="angularjs_sample.css"> <script src="http://code.angularjs.org/1.2.1/angular.min.js"></script> <script type="text/javascript"> function MemoCtrl($scope) { //メモの配列データ $scope.memoList = [{content: "一つ目のメモです", createdAt: (new Date()).toLocaleString()}, {content: "二つ目のメモです", createdAt: (new Date()).toLocaleString()}]; //フォームを表示するかしないかのフラグ $scope.showForm = false; //入力中のデータ $scope.inputtingMemo = null; //追加ボタンがクリックされたときの挙動 $scope.startAdd = function () { $scope.showForm = true; }; //作成処理 $scope.addMemo = function () { $scope.inputtingMemo.createdAt = (new Date()).toLocaleString(); //作成日付を入れる $scope.memoList.push(angular.copy($scope.inputtingMemo)); //データソース配列に追加 $scope.inputtingMemo = null; //フォームを初期化 $scope.showForm = 0; //フォームを閉じる }; } </script> </head> <body> <div class="memoBox" ng-controller="MemoCtrl"> <div class="header"> <h1>メモ</h1> <p class="addButton"> <button ng-click="startAdd()">追加</button> </p> </div> <div class="input" ng-show="showForm"> <form name="inputForm"> <p> <textarea name="content" ng-model="inputtingMemo.content" required ng-maxlength="200"></textarea> <!-- ① --> <span class="error" ng-show="inputForm.content.$error.required">入力してください</span> <!-- ② --> <span class="error" ng-show="inputForm.content.$error.maxlength">200文字以内で入力してください</span> <!-- ③ --> </p> <p> <button ng-click="showForm = false">キャンセル</button> <button ng-click="addMemo()" ng-disabled="!inputForm.$valid">作成</button> <!-- ④ --> </p> </form> </div> <div class="list"> <div class="listOpBox"> <p class="search"><input type="text" name="search" placeholder="検索"></p> <p class="sort"> <button>新しい順</button> <button>古い順</button> </p> </div> <ul class="record"> <li ng-repeat="memo in memoList"> <p class="content" ng-bind="memo.content"></p> <p class="op">{{memo.createdAt}} <a href="#">編集</a> <a href="#">削除</a></p> </li> </ul> </div> </div> </body> </html> |
① テキストエリアにrequiredとng-maxlengthを指定します。
②③ ng-showを使ってrequiredとmaxlengthがそれぞれエラーが存在するときにエラーメッセージを表示するようにしています。
④ ng-disabledを使ってフォーム全体がエラーがある場合はボタンをdisabledにしています。
動作するサンプルはこちら
入力フォームに必須チェック、文字長(200文字以内)チェックを入れてエラーがある場合は
エラーメッセージを表示して作成ボタンが押せないようにしています。
STEP5 検索とソート
AngularJSにはフィルターという便利な機能があります。
パイプ繋ぎで絞り込みをかけたり、データを加工したりが簡単にできるようになっています。
以下の例ではテキストボックスに入力された文字をuppercaseフィルターで大文字にして出力しています。
1 |
<input ng-model="filterTest">{{ filterTest | uppercase}} |
ソースは以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
<!doctype html> <html ng-app> <head> <meta charset="utf-8"> <link rel="stylesheet" href="angularjs_sample.css"> <script src="http://code.angularjs.org/1.2.1/angular.min.js"></script> <script type="text/javascript"> function MemoCtrl($scope) { //メモの配列データ $scope.memoList = [{content: "一つ目のメモです", createdAt: (new Date()).toLocaleString()}, {content: "二つ目のメモです", createdAt: (new Date()).toLocaleString()}]; //フォームを表示するかしないかのフラグ $scope.showForm = false; //入力中のデータ $scope.inputtingMemo = null; //追加ボタンがクリックされたときの挙動 $scope.startAdd = function () { $scope.showForm = true; }; //作成処理 $scope.addMemo = function () { $scope.inputtingMemo.createdAt = (new Date()).toLocaleString(); //作成日付を入れる $scope.memoList.push(angular.copy($scope.inputtingMemo)); //データソース配列に追加 $scope.inputtingMemo = null; //フォームを初期化 $scope.showForm = 0; //フォームを閉じる }; } </script> </head> <body> <div class="memoBox" ng-controller="MemoCtrl"> <div class="header"> <h1>メモ</h1> <p class="addButton"> <button ng-click="startAdd()">追加</button> </p> </div> <div class="input" ng-show="showForm"> <form name="inputForm"> <p> <textarea name="content" ng-model="inputtingMemo.content" required ng-maxlength="200" placeholder="200文字以内で入力してください"></textarea> <span class="error" ng-show="inputForm.content.$error.required">入力してください</span> <span class="error" ng-show="inputForm.content.$error.maxlength">200文字以内で入力してください</span> </p> <p> <button ng-click="showForm = false">キャンセル</button> <button ng-click="addMemo()" ng-disabled="!inputForm.$valid">作成</button> </p> </form> </div> <div class="list"> <div class="listOpBox"> <p class="search"><input type="text" placeholder="検索" ng-model="search"></p> <!-- ① --> <p class="sort"> <button ng-click="sortkey='createdAt';reverse=true;">新しい順</button> <!-- ② --> <button ng-click="sortkey='createdAt';reverse=false;">古い順</button> <!-- ③ --> </p> </div> <ul class="record"> <li ng-repeat="memo in memoList | filter:search | orderBy:sortkey:reverse | limitTo:4"> <!-- ④ --> <p class="content" ng-bind="memo.content"></p> <p class="op">{{memo.createdAt}} <a href="#">編集</a> <a href="#">削除</a></p> </li> </ul> </div> </div> </body> </html> |
①④ テキストボックスで入力された文字で絞り込まれるようにfilterフィルターを使ってます。
②③④ ソートを掛けるためさらにパイプでつなぎ<button>をクリックされたときにソートキー、昇降を指定しています。
動作するサンプルはこちら
検索とソートができるようになっています。(ソートはメモを追加しないと動作がわからないと思いますが)
最後に
残りの要件である編集と削除を実装はこちらにサンプルを置いておきます。
今回使った機能はまだまだAngularJSの一部に過ぎません。
URLマッピングやもっとちゃんとしたMVCなどは当然、ユニットテストのためのライブラリも備えています。
英語ではありますがリファレンスがしっかりしているので、情報不足で困ることはなさそうです。
いかがでしたでしょうか?
jQueryを使って書いたりするより、かなりシンプルに書けてJavaScriptもHTMLも可読性・保守性が向上すると感じました。
機会があれば弊社のサービスでも使ってみたいと思います。
今回は以上になりますが、アライドアーキテクツではエンジニアを随時募集しております。
興味があればぜひこちらの採用サイトまで。
最近ではコードを書く機会がめっきり減って来てプログラマー35歳限界説に恐怖しています。 このブログを口実に無理矢理新しい技術に触れていきたいと思っています。