前回、必要なコンポーネントの設定・作成をしたので、アプリとしての機能を作っていきます。

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は同じようなコーディングが必要になりがちですが、

自動で作成されないのでこの辺をなんとかしたい所です。

ソースはこちら