スクラッチからのHello, ASP.NET & AngularJS ビューとまとめ編

[mochalog](http://blog.mochaware.jp/20140215/540.html)からの転載です。

AngularJSとASP.NET MVC (Razor)を使ってビューを作成します。
AngularJSを使うとHTML要素とJavaScriptオブジェクトや関数とのバインドが簡単にできます。
Razorを使うととてもシンプルなやり方でビューの構造化ができます。

## ASP.NET MVCのControllerの作成

ControllersフォルダにHomeControllerクラスを作成します。

AngularJSだけでビューを作るのであれば必要ありませんが、
Razorを使わないのはもったいないのでView()を呼ぶだけのアクションを実装します。

```csharp
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}
```

## ビューの作成

Viewsフォルダ下にHomeフォルダを作成します。
作成したHomeフォルダ下にIndex.cshtmlファイルを作成します。
Scriptsフォルダ下にtodo.jsファイルを作成します。

それぞれ見比べながら見た方がわかりやすいので両方の抜粋を載せます。
Index.cshtmlのポイントは`ng-*`な属性と`{{}}`なところ、
todo.jsのポイントは`$scope`にいろいろ値や関数を設定してるところ、です。
それぞれに対応づいた値や関数名があるのがわかるかと思います。

これで、TodoCtrlで値を適当にいじると、値や関数にバインドされたところの表示が更新されます。
びっくりですね。

```html
@* 省略 *@
<!doctype html>
<html ng-app="todoApp">
<head>
    @* 省略 *@
</head>

<body>
    @* 省略 *@
    <div class="container">
        <h1 class="page-header">Todo</h1>

        <div ng-controller="TodoCtrl">
            <span>{{remaining()}} of {{todos.length}} remaining</span>
            [ <a href="" ng-click="archive()">archive</a> ]

            <p ng-show="todos === null">データのロード中です...</p>

            <ul class="list-unstyled" ng-show="todos !== null">
                <li ng-repeat="todo in todos" class="checkbox">
                    <label>
                        <input type="checkbox" ng-model="todo.IsDone" ng-change="updateTodo(todo)">
                        <span class="done-{{todo.IsDone}}">{{todo.Text}}</span>
                    </label>
                </li>
            </ul>

            <form class="form-inline" ng-submit="addTodo()">
                <input class="form-control" type="text" ng-model="todoText" size="30"
                       placeholder="add new todo here">
                <input class="btn btn-primary btn-sm" type="submit" value="add">
            </form>
        </div>
    </div>
</body>
</html>
```

```javascript
angular.
    module("todoApp", []).
    controller("TodoCtrl", ["$scope", "$http", function ($scope, $http) {
        // 値
        $scope.todos = null;
        $scope.todoText = "";

        // 関数
        $scope.addTodo = function () {
            // 省略
        };

        $scope.remaining = function () {
            // 省略
        };

        $scope.archive = function () {
            // 省略
        };

        $scope.updateTodo = function (todo) {
            // 省略
        };

        // 初期化処理
        // 省略
    }]);

```

### ng-app

#### Index.cshtml
`ng-app`属性でこのページのアプリケーション名を指定します。
今回の例でもhtml要素につけているように、一般的にhtml要素やbody要素に書きます。

#### todo.js

`angular.module("todoApp", [])`でアプリケーションを作成します。

### ng-controller

#### Index.cshtml

`ng-controller`属性でコントローラをビューに割り当てます。
AngularJSのコントローラはHTML要素とJavaScriptの関数や値を簡単にバインドできます。

コントローラという言葉がASP.NETでもAngularJSでも出てきますが、
AngularJSのコントローラはクライアントサイドの入出力値を管理して、
サーバサイドのASP.NET Web APIコントローラを呼び出すもの、という認識でいいです。

今回の例ではTodoCtrlというコントローラをdiv要素に割り当てています。
div要素内のHTML要素ではTodoCtrlが持つ関数や値をバインドして使っています。

#### todo.js

`controller("TodoCtrl", [...`でTodoCtrlを作成しています。
引数に`$scope`とか`$http`とかありますが、`$scope`はお決まりで書いておく、
`$http`はAjax呼び出ししたいときは書かないといけないもの、と思っておいてください。

### 関数や値のバインド

#### Index.cshtml

TodoCtrlを割り当てたdiv要素内のHTML要素に`{{}}`とか`ng-*`といった記述があります。
これでHTML要素とTodoCtrlが持つ値や関数をバインドします。

`{{}}`はコントローラが持つ値を出力します。

`ng-click`や`ng-submit`は要素がクリックされたり、サブミットされたときに呼ばれる関数をバインドできます。

`ng-model`は要素にコントローラが持つ値をバインドします。
input、textarea、selectといった入力のための要素にバインドして、
ビューへのユーザ入力をバインドした値に反映したり、
逆にバインドした値が変更されたときにビューに反映したりしてくれます。
今回の例では`type="checkbox"`や`type="text"`なinput要素に値をバインドしています。

#### todo.js

TodoCtrlが持つ関数や値を定義します。
コントローラが持つ値や関数は`$scope`に持たせます。

## CSSの作成

Contentフォルダ下にtodo.cssを作成します。
チェックされたtodoのスタイルを定義します。

```css
.done-true {
    text-decoration: line-through;
    color: grey;
}
```

Index.cshtmlの`<span class="done-{{todo.IsDone}}">{{todo.Text}}</span>`で、
todo.IsDoneがtrueのときspanのclassが"done-true"となるので、このスタイルが適用されます。
ちなみにこの記述のせいで、Visual Studioのブラウザリンクを有効にするとエラーが表示されます。。。

## AngularJSのコントローラの実装

todo.jsにAngularJSのコントローラを実装します。
前述したtodo.jsの全ソースは以下のとおり。

$scope.todosにtodoの配列、$scope.todoTextにテキスト入力値を持たせています。
各関数では`$http`のメソッドを使って、Web APIのアクションを呼んだりしています。
remaining()はサーバに問い合わせる必要がないので、TodoCtrlだけで処理が完結しています。
初期化ではWeb APIからデータを取得して$scope.todosに格納しています。

```javascript
angular.
    module("todoApp", []).
    controller("TodoCtrl", ["$scope", "$http", function ($scope, $http) {
        // 値
        $scope.todos = null;
        $scope.todoText = "";

        // 関数
        $scope.addTodo = function () {
            var todo = {
                IsDone: false,
                Text: $scope.todoText
            };
            $scope.todos.push(todo);
            $scope.todoText = "";
            $http.post("/api/todos", todo).success(function (data) {
                todo.Id = JSON.parse(data);
            });
        };

        $scope.remaining = function () {
            var count = 0;
            angular.forEach($scope.todos, function (todo) {
                count += todo.IsDone ? 0 : 1;
            });
            return count;
        };

        $scope.archive = function () {
            var oldTodos = $scope.todos;
            $scope.todos = [];
            angular.forEach(oldTodos, function (todo) {
                if (!todo.IsDone) $scope.todos.push(todo);
            });
            $http.delete("/api/todos/archive");
        };

        $scope.updateTodo = function (todo) {
            $http.put("/api/todos", todo);
        };

        // 初期化
        $http.get("/api/todos").success(function (data) {
            $scope.todos = data;
        });
    }]);
```


## Razorと組み合わせる

Razorのヘルパーや部分ビューももちろん使えます。
AngularJSにも構造化するためのメカニズムはあるのですが、
C#とかRazorに慣れてるとこっちの方が楽ですね。

分割したIndex.cshtmlの全ソースを以下に載せます。
Index.cshtmlは以下のとおり。

```html
@{
    var title = "Hello, ASP.NET & AngularJS!";
    var message = "ASP.NETとAngularJSで作るWebアプリのサンプルです。";
}

<!doctype html>
<html ng-app="todoApp">
<head>
    <meta charset="utf-8" />
    <title>@title</title>
    <script src="@Url.Content("~/Scripts/angular.js")"></script>
    <script src="@Url.Content("~/Scripts/jquery-2.1.0.js")"></script>
    <script src="@Url.Content("~/Scripts/bootstrap.js")"></script>
    <script src="@Url.Content("~/Scripts/todo.js")"></script>
    <link rel="stylesheet" href="@Url.Content("~/Content/bootstrap.css")">
    <link rel="stylesheet" href="@Url.Content("~/Content/todo.css")">
</head>

<body>
    @Jumbotron(title, message)

    <div class="container">
        <h1 class="page-header">Todo</h1>

        <div ng-controller="TodoCtrl">
            <span>{{remaining()}} of {{todos.length}} remaining</span>
            [ <a href="" ng-click="archive()">archive</a> ]

            @Html.Partial("_TodoList")

            @Html.Partial("_AddTodoForm")
        </div>
    </div>
</body>
</html>

@helper Jumbotron(string title, string message)
{
    <div class="jumbotron">
        <h1>@title</h1>
        <p>@message</p>
    </div>
}
```

_TodoList.cshtmlは以下のとおり。

```html
<p ng-show="todos === null">データのロード中です...</p>

<ul class="list-unstyled" ng-show="todos !== null">
    <li ng-repeat="todo in todos" class="checkbox">
        <label>
            <input type="checkbox" ng-model="todo.IsDone" ng-change="updateTodo(todo)">
            <span class="done-{{todo.IsDone}}">{{todo.Text}}</span>
        </label>
    </li>
</ul>
```

_AddTodoFormは以下のとおり。

```html
<form class="form-inline" ng-submit="addTodo()">
    <input class="form-control" type="text" ng-model="todoText" size="30"
           placeholder="add new todo here">
    <input class="btn btn-primary btn-sm" type="submit" value="add">
</form>
```

## 作成したTodoアプリのダウンロード

完成したTodoアプリが必要な方は、
[http://blog.mochaware.jp/?attachment_id=661](http://blog.mochaware.jp/?attachment_id=661)
よりダウンロードしてください。

## まとめ

以上、駆け足ですがASP.NETとAngularJSでTodoアプリを作ってみました。

* ASP.NET Web APIを使うとC#、CoCで快適にHTTPなサービスのAPIを作れます。JSONでもらったり返したり余裕です
* AngularJSを使うとHTML要素とJavaScriptオブジェクトや関数とのバインドが簡単にできます。JSONで呼び出したり受け取ったりも余裕です
* ASP.NET MVC (Razor)を使うとビューの構造化とか楽です。説明してませんがバンドルとか使うとミニファイとかも楽です。SPAじゃなくてちょっと画面遷移があるときもC#、CoCで快適です

こんな感じが伝わればと思います。

あと、実際にASP.NET + AngularJSで作った、参考になるリンクをまとめて共有するサービス、
[Refs](http://refs.mochaware.jp)もよろしくおねがいします。