2014/05/25追記
こういう書き方も出来ると紹介してもらいました。
@okazuki $scopeのメソッドの型をFunctionにしたくない場合 URL こーゆー感じで書くとコピペで済む+型チェックの恩恵が受けられるのでよいと思います。
2014-05-25 02:06:40 via twicli to @okazuki
はじめに
JavaScriptでSPA作るのにはAngularJSがいいらしいということで、とりあえずシンプルな例として、勉強がてら以下のページのしょっぱなにあるTodoアプリをTypeScriptで書いてみました。
プロジェクトの作成
TypeScriptHTMLApp1という名前(名前つけるのさぼった)でプロジェクトを作成して、NuGetから以下のライブラリを追加します。
- angularjs
- angularjs.TypeScript.DefinitelyTyped
- Twitter.Bootstrap
Twitter.Bootstrapは、見た目をちょっと変えようかなと思ったからです。
コントローラの作成
AngularJSをTypeScriptで作るには、とりあえず以下の手順を踏むのがよさそうに感じました。
- 使用するデータの型の作成
- $scope用のインターフェースの定義
- コントローラの作成
順番にTodoアプリでやってみます。
今回のTodoアプリでは、テキストと完了したかどうかをデータとして持つので、そのクラスを定義します。クラス名はTodoItemにしました。
class TodoItem { text: string; done: boolean; }
そして、スコープを定義します。スコープはng.IScopeを拡張したインターフェースとして定義します。インターフェース名はTodoScopeにしました。
interface TodoScope extends ng.IScope { todos: Array<TodoItem>; todoText: string; addTodo: Function; remaining: Function; archive: Function; }
関数は、全部Functionとして型指定するのが、個人的にちょっと気に入らないです。
そして、コントローラクラスです。コントローラは、コンストラクタでスコープを受け取り、スコープに必要なデータの設定と、スコープにメソッドの実体を設定します。
class TodoCtrl { constructor(private $scope: TodoScope) { $scope.todos = [ { text: "AngularJSの学習", done: true }, { text: "AngularJSのアプリケーション構築", done: false } ]; $scope.addTodo = angular.bind(this, this.addTodo); $scope.remaining = angular.bind(this, this.remaining); $scope.archive = angular.bind(this, this.archive); } addTodo(): void { this.$scope.todos.push({ text: this.$scope.todoText, done: false }); this.$scope.todoText = ""; } remaining(): number { var count = 0; angular.forEach(this.$scope.todos, (todo: TodoItem) => { count += todo.done ? 0 : 1; }); return count; } archive(): void { var old = this.$scope.todos; this.$scope.todos = []; angular.forEach(old, (todo: TodoItem) => { if (!todo.done) { this.$scope.todos.push(todo); } }); } }
angular.bindを使ってコントローラに定義したメソッドのthisをバインドしてスコープのメソッドに設定してると思われます。
app.tsの全体は以下のようになりました。
class TodoItem { text: string; done: boolean; } interface TodoScope extends ng.IScope { todos: Array<TodoItem>; todoText: string; addTodo: Function; remaining: Function; archive: Function; } class TodoCtrl { constructor(private $scope: TodoScope) { $scope.todos = [ { text: "AngularJSの学習", done: true }, { text: "AngularJSのアプリケーション構築", done: false } ]; $scope.addTodo = angular.bind(this, this.addTodo); $scope.remaining = angular.bind(this, this.remaining); $scope.archive = angular.bind(this, this.archive); } addTodo(): void { this.$scope.todos.push({ text: this.$scope.todoText, done: false }); this.$scope.todoText = ""; } remaining(): number { var count = 0; angular.forEach(this.$scope.todos, (todo: TodoItem) => { count += todo.done ? 0 : 1; }); return count; } archive(): void { var old = this.$scope.todos; this.$scope.todos = []; angular.forEach(old, (todo: TodoItem) => { if (!todo.done) { this.$scope.todos.push(todo); } }); } }
Viewの作成
Viewは特に例と変わらず。app.cssにTodoのチェックが入ったときのスタイルを例の通り定義します。
.done-true { text-decoration: line-through; color: gray; }
htmlはTwitter Bootstrapを使うようにする点以外は、サンプルと同じです。
<!DOCTYPE html> <html lang="ja" ng-app> <head> <meta charset="utf-8" /> <title>Todo sample app</title> <link rel="stylesheet" href="app.css" type="text/css" /> <link href="Content/bootstrap.min.css" rel="stylesheet" /> <script src="Scripts/jquery-1.9.0.min.js"></script> <script src="Scripts/bootstrap.min.js"></script> <script src="Scripts/angular.min.js"></script> <script src="app.js"></script> </head> <body> <div class="jumbotron"> <h1>Todo</h1> </div> <div class="container" ng-controller="TodoCtrl"> <div class="row"> <div class="col-md-1"> <span>残り:{{remaining()}}/{{todos.length}}</span> </div> <div class="col-md-1"> [<a href="" ng-click="archive()">完了</a>] </div> </div> <div class="row"> <div class="col-md-12"> <ul> <li ng-repeat="todo in todos"> <input type="checkbox" ng-model="todo.done" /> <span class="done-{{todo.done}}">{{todo.text}}</span> </li> </ul> </div> </div> <div class="row"> <div class="col-md-12"> <form ng-submit="addTodo()"> <input type="text" ng-model="todoText" size="30" placeholder="新しいTODOを追加" /> <input class="btn" type="submit" value="追加" /> </form> </div> </div> </div> </body> </html>
{{ }}を使ってバインドしたり、ng-***で色々やるみたいですが、Visual Studioでも{{ }}の中までは補完してくれないので、ちょっとストレス…。
実行結果
いい感じのTodoになりました。
これだけで、コレクションのバインドとかできるのは強力ですね。
まとめ
自分で色々やるのに比べたら、こいつを使うのがいいのかなあと思ったり思わなかったり。でもとっかかりは、いい感じなので、もう少しやってみようと思います。