Edited at

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

More than 3 years have passed since last update.

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

AngularJSを触り始めてから3ヶ月。Javascriptもちょっとしかわからなかったのでいろいろと行き当たりばったりでした。

自分的に最初に知っておけば良かったこととTipsみたいなものをまとめたのはこの記事です。3ヶ月の初心者なので、気になるところかここ間違ってるよとかここ日本語変だよなどがありましたら遠慮なく突っ込んでください。

ざっくり書いてます。大体どこでもある情報を書いてるのでご了承ください。


AngularJSは


MVWパターンのフレームワーク

MVW = Model-View-Whatever


AngularJSの開発者は「MV*について議論するのは時間の無駄だから、そんな暇があったらコードを書け。MV*の*の部分なんて"Whatever"でいいんだ。」という主張のようです。1



主の特徴


  • DI(依存性注入)

  • 双方向データバインディング

  • ディレクティブ自作できる

双方向データバインディングぱっと見よくわからないけど、 画像を見た方がわかりやすい。公式デベロッパーガイドはすごく充実してるので、一回読むことをお勧めです。


ここの$scopeの中身を見たい


  1. ブラウザで調べたいところを右クリック

  2. 「要素を検証」を選択

  3. inspector consoleでangular.element($0).scope()を入力


生のテンプレートを見せたくない時はngCloak

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

<div ng-cloak>{{ 'hello' }}</div>

<div class="ng-cloak">{{ ' world' }}<div>


ブラウザによって、このCSSルールが読み込まれた際に、ngCloakとしてタグ付けされた全てのHTML要素(その子要素を含む)は非表示にされます。 Angularはテンプレートのコンパイル中にこのディレクティブを見つけると、ngCloak属性を削除し、 コンパイルされた要素が表示されるようにします。2



$filterをgrepとして使える

ng-repeat="name in names | filter:searchTtext"

上記の使い方の方が馴染んでるけど

controllerの中で配列から一行を捜し出したい時も役に立つ。

例えばnamesの配列からfirstname=Foo, lastname=Barのデータを探す時:

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でやるとなんかうまくいかない...

var data = {bid: 1};

$http({
method: 'post',
url: url,
data: data,
}).then(
function (response) {
// success
}, function (response) {
// fail
}
);

もしくは

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

ん?Request Payload?普段よく見たのと違うよ

普段はこのような気がする

Imgur

よく見たら、\$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の形式

である必要がありますね。


リクエストごとに変更

ではこれはどうた!

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で変更

普通ならこの方法でいいでしょう

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


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 なんてこわくない


コンパイラーについて

前提

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 3


$broadcast, $emit



  • $broadcast子方向に伝播(自分も含む)


  • $emit親方向に伝播(自分も含む)

$rootScope.$emitでサービス間のイベント伝達ができる

画像を見てみましょう


学習リソース

ここまで書いたけどいいまとめを見つけました。

AngularJSを使い始めるときに見ると良いサイト集


最後に

最後まで読んでいただいてありがとうございました。

まとめ的に書きたいけどあまりまとまりませんでしたけど、少しでも役に立てたらうれしいです。