なんだかんだでたどり着いたAngularの書き方

[AngularJS Advent Calendar 2015](http://qiita.com/advent-calendar/2015/angularjs) 24日目の投稿です。

知人からどんな感じで Angular を書いてるのか尋ねられたので、ボクはこうしてますよという投稿です。良い例か悪い例かはボク自身判断できないのですが、参考になればと。

## とりあえずコード全部出す
ちょっと長いですが、全部出します。内容はただのTodoでRESTfulに通信してます。一覧をクリックすると編集でき、blur のタイミングで保存しにいくようにしてあります。ファイルはめんどうなのでわけてません。

```js
(function(){
  'use strinoct';
  
  angular.module('MyApp', ['ngResource'])
    .controller('SampleTodoCtrl', ['$scope', 'SampleTodoService', function($scope, SampleTodoService){
      
      var vm = $scope.vm = {};
      
      function init(){
        SampleTodoService.getTodo(vm);
      }
      
      vm.clickAddBtn = function(){
        SampleTodoService.addTodo(vm);
      };

      vm.toEdit = function(index){
        vm.todoList[index].edit = true;
      };

      vm.editDone = function(todo){
        todo.edit = false;
        SampleTodoService.editTodo(vm, todo);
      };

      vm.clickDeleteBtn = function(index){
        SampleTodoService.deleteTodo(vm, index);
      };
      
      init();
    }])
    .factory('SampleTodoService', ['SampleTodoApi', function(SampleTodoApi){
      var factory = {};
      
      var api = SampleTodoApi.getApi();
      
      factory.getTodo = function(vm){
        api.get(function(result){
          vm.todoList = result.todoList || [];
        })
      };
      
      factory.addTodo = function(vm){
        api.save(vm.todo, function(){
          vm.todoList.push(angular.copy(vm.todo));
          vm.todo = '';
        });
      };
      
      factory.editTodo = function(vm, editTodo){
        api.save(editTodo);
      };
      
      factory.deleteTodo = function(vm, index){
        var deleteTodo = vm.todoList[index];
        api.delete(deleteTodo.id, function(){
          vm.todoList.splice(index, 1);
        });
      };
      
      return factory;
    }])
    .factory('SampleTodoApi', ['$resource', function($resource){
      var factory = {};
      
      factory.getApi = function(){
        return $resource('/api/todo');
      };
      
      return factory;
    }])
    
})();
```

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.0/angular.min.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.0/angular-resource.js""></script>
  <script src="app.js"></script>
  <title>code sample</title>
</head>
<body ng-app="MyApp">
  
  <div ng-controller="SampleTodoCtrl">

    <div ng-form="sampleForm">
      <input type="text" name="todo" ng-model="vm.todo.title">
      <button ng-click="vm.clickAddBtn()">Add</button>

      <ul>
        <li ng-repeat="todo in vm.todoList" ng-form="sampleListForm">
          <div>
            <span ng-if="!todo.edit" ng-click="vm.toEdit($index)">{{todo.title}}</span>
            <span ng-if="todo.edit"><input type="text" name="editTodo" ng-model="todo.title" ng-blur="vm.editDone(todo)"></span>
            <button ng-click="vm.clickDeleteBtn($index)">削除</button>
          </div>
        </li>
      </ul>
    </div>
    
  </div>
  
</body>
</html>
```

## 結果こうなりました

### $scope 直下にプリミティブ型のプロパティを生やさない
これはよくいわれているので、対応している人も多いはず。スコープの継承で、親子スコープ間でデータ共有できなくなります。
そのため、初期化時には

```
var vm = $scope.vm = {};
```
とします。

また、コントローラ内で `$scope` を書くのめんどうなので、ショートカットもつくっておきます。

### コントローラの役割を厳格にする

昔はコントローラの引数に複数のサービスを読んで組み合わせていたんですけど、どうしても「サービス1の結果に対して分岐処理をする」みたいになってしまって、結果ロジック書いちゃってるじゃん！ってなってました。コントローラを薄くするためにもコントローラの役割を以下のように決めてしまいました。

コントローラは、

* スコープの初期化
* ビューからのイベントを補足して、サービスの該当処理を呼び出す
* サービス（非同期通信など）で取得したデータをビューに渡す

のみ。それ以外はさせないようにしてます（フラグ値をかえすくらいならコントローラでやってもいいかなとは思ってます）。

```
  vm.clickAddBtn = function(){
    SampleTodoService.addTodo(vm);
  };
```
要は、追加ボタンが押されたら、Todoを追加する処理してね、ってだけです。

### サービスはコントローラに対して 1:N
コントローラにロジックを書けなくなったので、サービスに書かざるを得ません。そのためコントローラから複数サービスを呼ぶのではなく、そのコントローラに対応するサービスを1つだけDIするようにして、そのサービス内で複数のサービスを組み合わせようにします。

```
.factory('SampleTodoService', ['SampleTodoApi', 'ここに', '並ぶ', function(SampleTodoApi, a, b){
  var factory = {};
  
  ...
```
この例だと `SampleTodoApi` だけですが、紹介している方法で書いていくと、この箇所でDIするサービスがたくさん並ぶことになります。

1:N の理由ですが、管理画面などの、入力/確認/完了/一覧 は共通的な機能が多いためです。
それぞれの画面に対して 1:1 で作るよりは、コードを使い回せるので楽できます。ただし、あまりにも1ファイルのサイズが大きくなった場合は 1:1 で作ったほうが良いです。

### vm をサービスに渡してしまう
これが一番のポイントかもしれません。通常はこうしたくなるはず。

```
  vm.clickAddBtn = function(){
    // ここでパラメータ作る
    var param = ...
    vm.todoList = SampleTodoService.addTodo(param); // パラメータ渡す
  };
```

こうすると、サービスのためにパラメータを加工して渡すことが多くなります。
ただ、それすらコントローラに書きたくないという意地！

で、考えた結果、 `vm` ごと渡してしまいます。パラメータは渡した先で作るようにします。そして、コントローラでビューのデータをセットするのではなく、サービスに `vm` を渡してセットしてもらいます。
初期化の際に、`vm = $socpe.vm` しているため、双方向データバインディングされた状態です。
なので、サービスでセットしたデータはビューに反映されます。 

「最小限のネストしたオブジェクト渡せばいいじゃん」、という方もいると思います、そう思います。
ただ、後から別のプロパティを参照しないといけなくなった場合、変更がめんどうなのでまるっと渡してしまう方針にしました。

で、結果これが良いな感じている今日この頃です。


失敗作として、

```
  .controller('SampleTodoCtrl', ['$scope', 'SampleTodoService', function($scope){
    $scope.vm = {};
    SampleTodoService.setViewModel($scope.vm);
    ...
  }]).
  .factory('SampleTodoService', ['SampleTodoApi', function(SampleTodoApi){
    var factory = {};
    var vm = {};
    
    factory.setViewModel = function(viewModel){
      vm = viewModel;
    };
    
    ...
```

のように、ビューモデル用の `setter` をつくろうとしたこともありました（サービスで保持させれば毎回 `vm` を渡さなくて良いので）。Angularの場合、サービスは `singleton` なので、毎回コントローラの初期化時にセットする必要性が生じてしまいます。忘れてしまって前回使用されたデータを参照してしまった、などのバグの温床になると考えやめました。

## まとめ
Angularユーザのみなさんはどんな感じで書いてますか？
せっかく晒したのでレビュープリーズ。