AngularJS 最初に知っておけば良かったことメモ

ニジボックスAdvent Calendar 2015 6日目の記事です。

AngularJSを触り始めてから３ヶ月。Javascriptもちょっとしかわからなかったのでいろいろと行き当たりばったりでした。
自分的に最初に知っておけば良かったこととTipsみたいなものをまとめたのはこの記事です。３ヶ月の初心者なので、気になるところかここ間違ってるよとかここ日本語変だよなどがありましたら遠慮なく突っ込んでください。
ざっくり書いてます。大体どこでもある情報を書いてるのでご了承ください。

## AngularJSは
### MVWパターンのフレームワーク
MVW = Model-View-Whatever
> AngularJSの開発者は「MV*について議論するのは時間の無駄だから、そんな暇があったらコードを書け。MV*の*の部分なんて"Whatever"でいいんだ。」という主張のようです。[^1]

[^1]: http://qiita.com/zoetro/items/a45dbc18bb2b22e944b2

### 主の特徴
- DI(依存性注入)
- 双方向データバインディング
- ディレクティブ自作できる

双方向データバインディングぱっと見よくわからないけど、 [画像を見た方がわかりやすい](http://js.studio-kingdom.com/angularjs/guide/databinding)。公式デベロッパーガイドはすごく充実してるので、一回読むことをお勧めです。

## ここの$scopeの中身を見たい
1. ブラウザで調べたいところを右クリック
2. 「要素を検証」を選択
3. inspector consoleでangular.element($0).scope()を入力

## 生のテンプレートを見せたくない時はngCloak
ページをロードするとき一瞬{{title}}みたいな生テンプレートが画面上に出てくるのはいやなので、ngCloakを使いましょう。

```html:
<div ng-cloak>{{ 'hello' }}</div>
<div class="ng-cloak">{{ ' world' }}<div>
```
> ブラウザによって、このCSSルールが読み込まれた際に、ngCloakとしてタグ付けされた全てのHTML要素(その子要素を含む)は非表示にされます。 Angularはテンプレートのコンパイル中にこのディレクティブを見つけると、ngCloak属性を削除し、 コンパイルされた要素が表示されるようにします。[^2]

[^2]: http://js.studio-kingdom.com/angularjs/ng_directive/ng_cloak

## $filterをgrepとして使える

```js:
ng-repeat="name in names | filter:searchTtext"
```
上記の使い方の方が馴染んでるけど
controllerの中で配列から一行を捜し出したい時も役に立つ。
例えばnamesの配列からfirstname=Foo, lastname=Barのデータを探す時：

```js:
findResult = $filter('filter')($scope.names, {firstname: 'Foo', lastname: 'Bar'});
target = findResult.length ? findResult[0] : 'Not found';
```

## ObjectかArrayを回したい時はangular.forEach
angular.forEach知らなかったので余計な処理を書いてました。
ObjectとArrayとも使えるので便利です。

## $httpでForm Dataデータ送信したい
普通にjQueryの`$.ajax`でできてるけど、angularの`$http`でやるとなんかうまくいかない...

```js:
var data = {bid: 1};
$http({
    method: 'post',
    url: url,
    data: data,
}).then(
    function (response) {
        // success
    }, function (response) {
        // fail
    }
);
```
もしくは

```js:
var data = {bid: 1};
$http.post(url, data).success(function(data, status, headers, config) {
	// success
}).error(function(data, status, headers, config) {
	// fail
});
```
phpの$_POSTでデータが取れない！なんでだって見たら
![Imgur](http://i.imgur.com/DSq4cWH.png)
ん？Request Payload?普段よく見たのと違うよ
普段はこのような気がする
![Imgur](http://i.imgur.com/tpXo7po.png)

よく見たら、\$httpのデフォルトは
`Content-Type: application/json`になってました。
Request Payloadは生のリクエストボディで
$_POSTからデータ取れないわけだ。
（ちなみにfile_get_contents("php://input")から取れる)
Form Dataの送信は
1. `Content-Type: application/x-www-form-urlencoded`
2. データは`a=foo&b=bar`の形式
である必要がありますね。

### リクエストごとに変更
ではこれはどうた！

```js:
var config = { 
    // set Content-Type header
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    // serialize data
    transformResponse: function(data) {
             if (typeof data === "undefined" || typeof data === "string")
                 return data;
             return $.param(data);
        }
};
$http.post(url, data, config).success(function(data, status, headers, config) {
	// success
}).error(function(data, status, headers, config) {
	// fail
});
```
うまくいきました。
でも毎回postするときこの変更を入れないといけないのは面倒くさいし
あとあとメンテし辛いので一箇所に集中して処理したほうがよさそうです。

### デフォルトを変更
`$httpProvider`に`$http`のデフォルトの動きが定義されている。
それを変更することで毎回同じことをする手間がなくなる。

#### $httpProvier.defaultsを一回のconfigで変更
普通ならこの方法でいいでしょう

```js:
angular.module('myApp', [])
.config(['$httpProvider', function ($httpProvider) {
    // set Content-Type header
    $httpProvider.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
    // serialize data
    $httpProvider.defaults.transformRequest.unshift(function (data, headersGetter) {
        if (typeof data === "undefined" || typeof data === "string")
            return data;
        return $.param(data);
    });
}]);
```
#### $httpProvier.interceptorsを利用
毎回$http呼ばれたとき介入してconfigを変更する
interceptorについてこの記事をお勧め：[Interceptors in AngularJS and Useful Examples](http://www.webdeveasy.com/interceptors-in-angularjs-and-useful-examples/)

```js:

angular.module('myApp', [])
.factory('postDataInjector', [function() {
    var postDataInjector = {
        request: function(config) {
            if (config.method == "POST") {
                // set Content-Type header
                config.headers["Content-Type"] = "application/x-www-form-urlencoded";
                // serialize data
                config.transformRequest.unshift(function (data, headersGetter) {
                    if (typeof data === "undefined" || typeof data === "string")
                        return data;
                    return $.param(data);
                });
            }
            return config;
        }
    };
    return postDataInjector;
}]);
.config(['$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('postDataInjector');
}]);
```


## よく使うテンプレートをDirective化すべき
ng-includeという手もあるけど
controller操作に関わるとややこしくなるので恐れずにDirective化しましょう。
[AngularJS Directive なんてこわくない](http://angularjsninja.com/blog/2013/11/20/angularjs-custom-directives/)

## コンパイラーについて
**前提**
Angularのコンパイラーが扱うのはHTMLの文字列じゃなくてDOM nodeです。

**手順**
1. DOMを辿り、Directiveを見つけたらlistに追加して集める。このlistを優先度でソートする。
2. compileフェーズ: list順にDirectiveのcompile functionを呼ぶ、compile functionごとにlink functionが返却される。これらのlink functionsは一個の大きいlink functionにまとめされる
3. linkフェーズ: compiler function全部走り終わったらまとめたlink functionを呼ぶ。link functionは大体DOM listenerの登録と Scopeの$watch登録をやっている

**なんでcompileフェーズとlinkフェーズを分けるんですか**
例えばng-repeatで毎回要素が追加されたとき、DOM要素を複製し、コンパイルし直ししたら処理が相当重くなる。compileフェーズでdirective共通コンパイル処理の結果(link function)を先に保存したら、要素が追加されたとき、DOM要素を複製してlinkするだけで終わり。なのでcompileフェーズとlinkフェーズを分けることによってパフォーマンスの向上が実現される。

## スコープについて
### $digestサイクル
全部のdirty checkがstaticになるまでdigestサイクルから離脱できない。
![digest circle](http://i.imgur.com/Ahkd2ky.png) [^3]
[^3]: https://docs.angularjs.org/guide/scope#integration-with-the-browser-event-loop

### $broadcast, $emit
- `$broadcast`子方向に伝播(自分も含む)
- `$emit`親方向に伝播(自分も含む)

`$rootScope.$emit`でサービス間のイベント伝達ができる
[画像を見てみましょう](http://www.bennadel.com/blog/2807-using-rootscope-emit-as-a-performance-optimization-in-angularjs.htm)

## 学習リソース
- [AngularJs Tutorial](https://docs.angularjs.org/tutorial)
- AngularJS Developer Guide [英語](https://docs.angularjs.org/guide)｜[日本語](http://js.studio-kingdom.com/angularjs/guide)
- [AngularJS Startup Advent Calendar 2013](http://qiita.com/advent-calendar/2013/angularjs-startup)
- [AngularJS Advent Calendar 2014](http://qiita.com/advent-calendar/2014/angularjs)
- [ドットインストール AngularJS入門](http://dotinstall.com/lessons/basic_angularjs) (若干古い)

ここまで書いたけどいいまとめを見つけました。
[AngularJSを使い始めるときに見ると良いサイト集](http://blog.fagai.net/2015/05/24/angularjs-best-sites/)

## 最後に
最後まで読んでいただいてありがとうございました。
まとめ的に書きたいけどあまりまとまりませんでしたけど、少しでも役に立てたらうれしいです。