ニジボックスAdvent Calendar 2015 6日目の記事です。
AngularJSを触り始めてから3ヶ月。Javascriptもちょっとしかわからなかったのでいろいろと行き当たりばったりでした。
自分的に最初に知っておけば良かったこととTipsみたいなものをまとめたのはこの記事です。3ヶ月の初心者なので、気になるところかここ間違ってるよとかここ日本語変だよなどがありましたら遠慮なく突っ込んでください。
ざっくり書いてます。大体どこでもある情報を書いてるのでご了承ください。
AngularJSは
MVWパターンのフレームワーク
MVW = Model-View-Whatever
AngularJSの開発者は「MVについて議論するのは時間の無駄だから、そんな暇があったらコードを書け。MVの*の部分なんて"Whatever"でいいんだ。」という主張のようです。1
主の特徴
- DI(依存性注入)
- 双方向データバインディング
- ディレクティブ自作できる
双方向データバインディングぱっと見よくわからないけど、 画像を見た方がわかりやすい。公式デベロッパーガイドはすごく充実してるので、一回読むことをお勧めです。
ここの$scopeの中身を見たい
- ブラウザで調べたいところを右クリック
- 「要素を検証」を選択
- 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でデータが取れない!なんでだって見たら
ん?Request Payload?普段よく見たのと違うよ
普段はこのような気がする
よく見たら、$httpのデフォルトは
Content-Type: application/json
になってました。
Request Payloadは生のリクエストボディで
$_POSTからデータ取れないわけだ。
(ちなみにfile_get_contents("php://input")から取れる)
Form Dataの送信は
Content-Type: application/x-www-form-urlencoded
- データは
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です。
手順
- DOMを辿り、Directiveを見つけたらlistに追加して集める。このlistを優先度でソートする。
- compileフェーズ: list順にDirectiveのcompile functionを呼ぶ、compile functionごとにlink functionが返却される。これらのlink functionsは一個の大きいlink functionにまとめされる
- 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サイクルから離脱できない。
3
$broadcast, $emit
-
$broadcast
子方向に伝播(自分も含む) -
$emit
親方向に伝播(自分も含む)
$rootScope.$emit
でサービス間のイベント伝達ができる
画像を見てみましょう
学習リソース
- AngularJs Tutorial
- AngularJS Developer Guide 英語|日本語
- AngularJS Startup Advent Calendar 2013
- AngularJS Advent Calendar 2014
- ドットインストール AngularJS入門 (若干古い)
ここまで書いたけどいいまとめを見つけました。
AngularJSを使い始めるときに見ると良いサイト集
最後に
最後まで読んでいただいてありがとうございました。
まとめ的に書きたいけどあまりまとまりませんでしたけど、少しでも役に立てたらうれしいです。