/* 初歩的なメモを、再確認の意味も含めて足し継ぎしていく覚書 */
ng-inspector for AngularJS
scope階層を表示してくれる狂おしいほど便利なChrome拡張。
$broadcastと$emitの使い分け
-
$broadcast ... 子スコープへ伝達(dispatches the event downwards to all child scopes)
- 例: $rootScopeから全スコープへ
-
$emit ... 親スコープへ伝達(dispatches the event upwards through the scope hierarchy).
- 例: DirectiveからControllerへ
覚え方(ゴリ押し): broadcast = 意味はテレビ・ラジオの放送。テレビ塔からご家庭のテレビへ配信されるイメージ
emit = 意味は(光・熱等の)放射。空気中へ立ち上るイメージ
[Directive] 隔離スコープ(isolated scope)へのプロパティ渡し
Directiveはデフォルトで親Controllerの$scopeを共有するが、scope:{}を指定することで、Directive内に隔離スコープを作ることができる。
その際scope:{} 内に指定されたプロパティ以外、継承されない。
不用意に親スコープを変更されたくない時など便利。
angular.module('app.directives',[])
.directive('myWidget', function(){
return {
// Directiveに隔離スコープを作る。
// scope:{} 内に指定されたプロパティ以外、継承されない。
scope:{
//親子スコープ間で双方向データバインディング
//親スコープのプロパティを[プロパティの名前]で渡す
// data-one="someVar"
dataOne: '=',
//親→子へプロパティを伝達。親が変われば子も変わる。逆は不可
//【!】必ずStringとして渡される。なので
// data-two="{{text}}"
//のように"{{}}"を使って渡す。
dataTwo: '@',
//親スコープで関数ORエクスプレッションを子から実行したい時
//例 エクスプレッション:
// data-three="count = count + value"
//例 関数:
// data-three="add(value)"
// ※この場合、子からは
// dataThree({value:22}) のように引数を渡す。
dataThree: '&',
//HTML側の属性(attribute)名を指定可能。
// attr-four='someVar2' と記述し、
// scope.dataFour とDirectiveからは呼び出す。
//覚え方は、scope:{}内に記述していることを思い出せば◎
dataFour: '=attrFour',
// ?を付加すると、任意になる(渡さなくても良い)
dataOptional: '@?'
},
link: function(scope, element, attr){
// ...
}
};
})
[Directive]プロパティの渡し方一覧(書き途中)
AngularJS Directive Attribute Binding Explanation by CMCDragonkai
めちゃくちゃありがたい記事があったので感謝しつつ自己消化。
$scope.models = {someObj: "like this"};
Objectに格納することは習慣づけておいたほうが吉。
1. 不変のStringを渡したい(Raw Attribute Strings)
link関数に渡される引数のattributesから取得する。
※ {{}}の使用はバージョンに要注意
<!-- $scope.someScopeParam = "another string"; -->
<my-widget data-one="some string" data-two="{{someScopeParam}}">
</my-widget>
myApp.directive('myWidget', function(){
return {
link: function(scope, element, attributes){
// "some string"
console.log(attributes.dataOne);
// ※ (ver1.2以降) "another string"
// ※ (それ以前)"{{someScopeParam}}"
console.log(attributes.dataTwo);
}
};
})
おまけ:なお、compile関数ではcompileが実行されていないので以下のように。
myApp.directive('myWidget', function(){
return {
compile: function(element, attributes){ // scopeが無い
console.log(attributes.dataOne); // "some string"
console.log(attributes.dataTwo); // "{{someScopeParam}}"
}
};
})
2. {{}}を渡して変更を監視したい (Observing Interpolated Strings)
attributes.$observeを使って、{{}}内のプロパティ変更による{{}}の変化を監視できる。
<!-- $scope.mutableParam = "変わる予定"; -->
<my-widget data-one="{{mutableParam}}"></my-widget>
myApp.directive('myWidget', function(){
return {
link: function(scope, element, attributes){
//変更を監視する
attributes.$observe('dataOne', function(value){
//"変わる予定" →(数秒後)→ "変わった!"
console.log(value);
});
}
};
})
おまけ: scope.$watchとの使い分け?
端的に言うと、{{}}が含まれているときはscope.$watchは使えない。
scope.$watchが使うのはscope内に存在するプロパティに対して。scope['{{someParam}}']はundefinedを返す。
attributes.$observeはDOM上のDirectiveの、attribute値の変化を監視したいときに使う。
3. 親スコープのプロパティ/エクスプレッションを監視したい
scope.$watchを使う。Directiveは親のscopeを共有していることがミソ。
<!-- $scope.mutableParam = "変わる予定"-->
<!-- $scope.models={someObject:"someText"} -->
<my-widget data-one="mutableParam" data-two="models.someObject"></my-widget>
myApp.directive('myWidget', function(){
return {
link: function(scope, element, attributes){
//以下はscope.$watch('mutableParam', function(){...});と同義であることに注目すると分かりやすい。
scope.$watch(attributes.dataOne, function(value){
console.log(value);
});
//scope.$watch('models.someObject', function(){...});と同義。
//scopeは親と共有されている。
scope.$watch(attributes.dataTwo, function(value){
console.log(value);
});
}
};
})
以下また時間が有る時に
4. Objectを渡したい→scope.$eval
5〜8. 隔離スコープを作りたい →scope:{}
(大筋は前セクションで書いた通り)
9. 隔離スコープを作らず双方向データバインディング→scope.$apply, scope.$eval, $parse
[ngController]$scopeの代わりにControllerAsを使う
最近知ってハマっているスタイル。$scopeを殆ど使わない。
myApp.controller('myCtrl',function(){
this.someFn = function(){
//なんかの関数
};
this.someVariable = "4";
});
<div ng-controller="myCtrl as ctrl">
{{ctrl.someVariable}}
<button ng-click="ctrl.someFn()">
ボタン
</button>
</div>
また、ngRouteやui-routerのconfigでもcontrollerAsというプロパティから設定できます。
Pros:
- 必然的にPrimitiveを使わないので、スコープ継承の問題が起こらない。(ngIf、ngRepeat内など隔離スコープをつくるDirective内から変数を呼びだそうとして上手くいかないAngularあるある)
- 擬似的にPublic(例:this.publicFn)とPrivate(例:function privateFn(){})のように表記分けができる。
- 入れ子コントローラでも簡単に親コントローラにアクセスできる
<div ng-controller="grandpaCtrl as grandpa">
<div ng-controller="motherCtrl as mom">
<div ng-controller="meCtrl as me">
<!-- 親コントローラーにアクセスできる -->
{{grandma.otoshidama}}
{{mom.meal}}
</div>
</div>
</div>
Cons:
- $scope.$watch('someVar',function{})…という表記はできない。
- [解決方法] $scope.$watch(function(){return this.anotherVar;},function(){})と書く。
結論
毎回ctrl.と前置きしなきゃいけなかったり、$watchで表記がめんどくさかったりしますが、うっかりおかしやすいスコープ継承ミスが断然減るので現状こちらのほうが良いなあという感想です。$scopeはやっぱり打ちづらいし…笑
あとDOM側のみで使うmodelを作りたい時とかに$scope使ってます。コントローラ側とビュー側で名前の重複を避けられていい感じです。新しいプロジェクトを初めて見る時等いかがでしょうか。
書きとめたいリストにも入ってますが、Directiveでもlinkの中身をcontrollerに移すと捗るんだなあ、というのがなんとなくわかってきました。親directiveのcontrollerに関数を置いて、requireで子directiveにcontrollerを渡すアレ。
イカ思いついたら随時
書き留めたいかもメモ
- Directiveへの値の渡し方一覧(着手)
- DirectiveのControllerについて (https://docs.angularjs.org/api/ng/service/$compile#-controller-
)