前回、必要なコンポーネントの設定・作成をしたので、アプリとしての機能を作っていきます。
todo.js
/Scripts/app/todo.jsを作成し、クライアントサイドのアプリ機能を書いていきます。
まぁ、書くといってもコピペなわけですが…
ただ、サンプルのtodo.jsそのままだと
1
|
Error: [ng:areq] Argument 'TodoCtrl' is not a function, got undefined
|
と言われて動きません。
angularjsのバージョンとの相性でしょうかね?
moduleを定義するように変更します。
今の段階ではこんな感じ。
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
|
/// <reference path="~/Scripts/angular.min.js" />
var todoApp = angular.module('todoApp', []);
todoApp.controller("TodoCtrl", function ($scope) {
$scope.todos = [
{ text: 'AngularJSの学習', done: true },
{ text: 'AngularJSのアプリケーション構築', done: false }
];
$scope.addTodo = function () {
$scope.todos.push({ text: $scope.todoText, done: false });
$scope.todoText = '';
};
$scope.remaining = function () {
var count = 0;
angular.forEach($scope.todos, function (todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.archive = function () {
var oldTodos = $scope.todos;
$scope.todos = [];
angular.forEach(oldTodos, function (todo) {
if (!todo.done) $scope.todos.push(todo);
});
};
});
|
JavaScriptでインテリセンス
Visual Studio 2013ではJavaScriptでもインテリセンスが効くようです。
これだけでもVersion Upの価値ありですな。
ただし、todo.jsの1行目のようにreference ディレクティブで参照設定的なことが必要です。
index.cshtml
index.cshtmlもサンプルからコピペしてきます。
サンプルからの主な変更点は、ng-appにモジュールの登録を追加、スクリプトの参照記述をbody閉じタグの直前にしたことぐらいでしょうか。
todo.jsはangular.jsの後に読み込まないとうまく動かないみたいです。
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
|
@{
Layout = null;
}
<!DOCTYPE html>
<html ng-app="todoApp">
<head>
<meta name="viewport" content="width=device-width" />
<title>AngularJS Sample SPA</title>
</head>
<body>
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
<span>残り:{{remaining()}}/{{todos.length}}</span>
[ <a href="" ng-click="archive()">完了</a> ]
<ul class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>
<form ng-submit="addTodo()">
<input type="text" ng-model="todoText" size="30"
placeholder="新しいTODOを追加">
<input class="btn-primary" type="submit" value="追加">
</form>
</div>
<!-- don't need Url.Content from MVC4 -->
<script src="~/Scripts/angular.js"></script>
<script src="~/Scripts/app/todo.js"></script>
</body>
</html>
|
とりあえず、動いてます。
当然、クライアント内(ブラウザ内)で動いてるだけなので、これをサーバサイドと連携するようにします。
基本的には前回やったようにngResourceを使います。
WebAPIバージョン
WebAPIを呼び出すように変更したバージョンがこちら。(ブログに張るには長いな…)
todoClassがWebAPIを呼び出す肝です。
modelの定義はサーバ側の定義(C#)が使われるので、メンバー名の大文字小文字が変更になります。
データ更新はinput要素のイベントハンドラではなく、watchCollectionを使ってデータ自体を監視するようにしてみました。
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
|
/// <reference path="~/Scripts/angular.min.js" />
/// <reference path="~/Scripts/angular-resource.js" />
var todoApp = angular.module('todoApp', ['ngResource']);
todoApp.controller("TodoCtrl", function ($scope, $resource) {
//WebAPI呼び出し用オブジェクト作成
var todoClass = $resource('/api/Todoes/:id', { id: '@Id' }
, { 'update': { method: 'PUT' } } //既定でPUT呼び出しが無いので定義
);
//ToDoリスト初期化
$scope.todos = todoClass.query();
// 各アイテムに変更監視ハンドラをセット
var deWatchTodo = new Array(); //ハンドラリセット用配列
$scope.$watchCollection("todos", function (newVal) {
//古いハンドラをリセット
angular.forEach(deWatchTodo, function (value, key) { value(); });
deWatchTodo = [];
// 監視関数をセット
angular.forEach(newVal, function (value, key) {
var deregistration = $scope.$watch("todos[" + key + "]", function (newVal, oldVal, scope) {
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) { //内容が変わっている場合だけWebAPIコール
todoClass.update(newVal.Id, newVal);
}
}, true);
deWatchTodo.push(deregistration); //リセット用に記録
});
});
// アイテム追加
$scope.addTodo = function () {
//WebAPIコール
todoClass.save({ Text: $scope.todoText, Done: false }, function (result) {
//成功したらクライアント側でも記録
$scope.todos.push(result);
});
$scope.todoText = '';
};
// 残り件数
$scope.remaining = function () {
var count = 0;
angular.forEach($scope.todos, function (todo) {
count += todo.Done ? 0 : 1;
});
return count;
};
// 終了済みアイテムを削除
$scope.archive = function () {
var oldTodos = $scope.todos;
$scope.todos = [];
angular.forEach(oldTodos, function (value, key) {
if (value.Done) { //終わってら
todoClass.delete({ id: value.Id }); //削除WebAPIコール
} else { //終わってなかったら
$scope.todos.push(value) //ToDoリストに戻す
}
});
};
});
|
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
|
@{
Layout = null;
}
<!DOCTYPE html>
<html ng-app="todoApp">
<head>
<meta name="viewport" content="width=device-width" />
<title>AngularJS Sample SPA</title>
</head>
<body>
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
<span>残り:{{remaining()}}/{{todos.length}}</span>
[ <a href="" ng-click="archive()">完了</a> ]
<ul class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.Done">
<span class="done-{{todo.Done}}">{{todo.Text}}</span>
</li>
</ul>
<form ng-submit="addTodo()">
<input type="text" ng-model="todoText" size="30"
placeholder="新しいTODOを追加">
<input class="btn-primary" type="submit" value="追加">
</form>
</div>
<!-- don't need Url.Content from MVC4 -->
<script src="~/Scripts/angular.js"></script>
<script src="~/Scripts/angular-resource.min.js"></script>
<script src="~/Scripts/app/todo.js"></script>
</body>
</html>
|
Visual Studioのテンプレートをうまく使うとRESTFullなWebAPIをさくっと作れて便利なような気がします。
呼び出し側のJavaScriptは同じようなコーディングが必要になりがちですが、
自動で作成されないのでこの辺をなんとかしたい所です。
ソースはこちら