Google 製の JavaScript MVC フレームワーク『AngularJS』で、簡単な ToDo アプリのサンプルを書いてみた。
<!DOCTYPE html> <html ng-app> <head> <meta charset="utf-8"> <title>AngularTodo</title> <script src="http://code.angularjs.org/angular-1.0.1.min.js"></script> <script> // モデル function Todo(text, done) { this.id = Todo.getNextId(); this.text = text; this.done = done || false; this.editing = false; } // 連番の ID を作成 Todo.getNextId = (function() { var _nextId = 0; var _getNextId = function() { _nextId++; return _nextId; }; return _getNextId; })(); // コントローラー function TodoCtrl($scope) { $scope.todos = [ new Todo("foo", true), new Todo("bar") ]; // 未完了の ToDo を数える $scope.remaining = function() { var count = 0; angular.forEach($scope.todos, function(todo) { count += todo.done ? 0 : 1; }); return count; }; // 完了した ToDo を削除 $scope.archive = function() { var oldTodos = $scope.todos; $scope.todos = []; angular.forEach(oldTodos, function(todo) { if (!todo.done) { $scope.todos.push(todo); } }); }; // ToDo 追加 $scope.addTodo = function() { $scope.todos.push(new Todo($scope.todoText)); $scope.todoText = ""; }; // ToDo の編集開始 $scope.editTodo = function(id) { var todo = findTodoById(id); todo.editing = true; }; // ToDo の編集終了 $scope.updateTodo = function(id) { var todo = findTodoById(id); todo.editing = false; }; // ToDo を削除 $scope.deleteTodo = function(id) { var index = findTodoIndexById(id); $scope.todos.splice(index, 1); }; // Todo を ID で検索してインデックスを取得 function findTodoIndexById(id) { var numId = Number(id); for (var i = 0, max = $scope.todos.length; i < max; i++) { var todo = $scope.todos[i]; if (todo.id === numId) { return i; } } return null; } // Todo を ID で検索 function findTodoById(id) { var index = findTodoIndexById(id); return $scope.todos[index]; } } </script> <style> .done-true { text-decoration: line-through; color: grey; } </style> </head> <body> <h2>Angular Todo</h2> <div ng-controller="TodoCtrl"> <span>{{remaining()}} of {{todos.length}} remaining</span> [<a href="" ng-click="archive()">archive</a>] <ul class="unstyled"> <li ng-repeat="todo in todos"> <!--通常表示するビュー--> <div ng-show="!todo.editing"> <input type="checkbox" ng-model="todo.done"> <span class="done-{{todo.done}}">{{todo.text}}</span> [<a href="" ng-click="editTodo(todo.id)">edit</a>] [<a href="" ng-click="deleteTodo(todo.id)">delete</a>] </div> <!--編集時に表示するビュー--> <div ng-show="todo.editing"> <input type="text" ng-model="todo.text"> <input type="button" ng-click="updateTodo(todo.id)" value="Finish"> </div> </li> </ul> <!--name を指定するとテンプレート内でアクセスできる--> <form name="form" ng-submit="addTodo()"> <input type="text" ng-model="todoText" size="30" placeholder="add new todo here" required> <!--todoText が空のときはボタンを押せないようにする--> <input class="btn-primary" type="submit" value="add" ng-disabled="form.$invalid"> </form> </div> </body> </html>
ToDo MVC と差別化するために、ToDo はその場で編集できるようにしている。編集画面の切り替えを、バインドしているモデルのプロパティ変更で行っているのは、AngularJS 力が足りなかったための苦肉の策。もっとキレイに実装できると思う。
今回のサンプルで使っている機能は、テンプレートとデータバインディング、あとフォームのバリデーション。AngularJS には他にもルーティングや国際化やコンポーネントなど、かなりの機能があるみたいなので、それらは追々試してみる。
作ってみての感想は、HTML テンプレート+データバインディングはやはり楽チン。Backbone.js だとモデルが変更されたらビューで DOM 操作して表示を変更していたけど、その部分を AngularJS が自動でやってくれるおかげでサンプルがサクッっと記述できた。
Knockout.js でもデータバインディングと HTML テンプレートは使えたけど、AngularJS はさらに多機能。正直、現時点では AngularJS ではなく Knockout.js を選択する理由が見当たらないな。
今は Backbone.js をメインで使っていて、Backbone.js のコンパクトさや Underscore.js の便利さをスゴク気に入っているんだけど、AngularJS に心揺れ動いている自分がいる。