AngularJSのdirective間の連携
僕なりによく使うなー、って方法を書いてみる。
親ディレクティブと子ディレクティブ間で通信をしたい場合
tabset要素とtab要素は親と子の関係になると言えると思う。
そしてtabset要素は今選択している要素が何なのかを管理して、tab要素に適切に伝えられる必要がある。
そんな時には、子ディレクティブ(tab)から親ディレクティブ(tabset)のAPIを利用するようにできれば良い。
そんな時に使うと良いのが、directiveのrequireだ。
requireは'^ディレクティブ名'と指定すると、親のコントローラを参照できるようにしてくれる。
^を付けない場合は同じ要素内の属性を探しにいく。
この記事がその辺の挙動がすごく分かりやすい。
AngularJS Directive なんてこわくない(その4) - AngularJS Ninja
例
あまり良い例じゃないかも知れないけど、「選択可能なリスト」でこれを実装してみる。
公式のディレクティブの解説を参考にしました。
(bootstrapを使っています。)
- main.html
<div class="row"> <selectable> <div class="list-group col-lg-6"> <selectable-list-item ng-repeat='item in items' item='item'></selectable-list-item> </div> </selectable> </div>
- コントローラ
angular.module('childDirectiveApp') .controller('MainCtrl', function ($scope) { 'use strict'; $scope.items = [ { title: 'test1', content: 'content1' }, { title: 'test2', content: 'content2' }, { title: 'test3', content: 'content3' }, ]; });
- ディレクティブ
angular.module('childDirectiveApp') .directive('selectable', [ function() { 'use strict'; return { restrict: 'E', scope: {}, controller: [function() { var listItems = []; this.add = function(listItem) { if (listItems.length === 0) { this.select(listItem); } listItems.push(listItem); }; this.select = function(listItem) { angular.forEach(listItems, function(item) { item.selected = false; }); listItem.selected = true; }; }], }; }]) .directive('selectableListItem', [ function() { 'use strict'; return { restrict: 'E', template: '<a href="" class="list-group-item" ng-click="select()" ng-class="{active: selected}">{{item.title}}</a>', replace: true, scope: {item: '='}, require: '^selectable', link: function(scope, element, attrs, cont) { cont.add(scope); scope.select = function() { cont.select(scope); }; } }; }]) ;
- 表示
selectable
directiveが親でselectableListItem
が子の関係になっている。
子ディレクティブ同士で通信をしたい場合
例えば、こういうふうにしたい時。
これならさっきの親子関係を維持したまま、子を一人増やすだけでいける。
例
- main.html
<div class="row"> <selectable> <div class="list-group col-lg-6"> <selectable-list-item ng-repeat='item in items' item='item'></selectable-list-item> </div> <selected-content class="col-lg-6"></selected-content> </selectable> </div>
- selected-content.html(テンプレート)
<form class="well"> <div class="form-group"> <label for="title">title:</label> <input id="title" type="text" class="form-control" placeholder="title" ng-model="item.title"> </div> <div class="form-group"> <label for="content">content:</label> <textarea id="content" class="form-control" placeholder="content" ng-model="item.content"></textarea> </div> <button class="btn btn-info" ng-click="save()">Save</button> </form>
- ディレクティブ
selected-content
ディレクティブ(子)を作る。
.directive('selectedContent', [ function() { 'use strict'; return { restrict: 'E', templateUrl: 'views/selected-content.html', replace: true, scope: {}, require: '^selectable', link: function(scope, element, attrs, cont) { scope.item = angular.copy(cont.selected().item); //初期選択を反映させるため scope.save = function() { cont.save(scope.item); }; scope.$on('selectedItemChanged', function(ev,item) { scope.item = item; }); } }; }])
selectable
ディレクティブ(親)に機能の追加をする。
.directive('selectable', [ function() { 'use strict'; return { restrict: 'E', scope: {}, controller: ['$rootScope', function($rootScope) { var listItems = []; this.add = function(listItem) { if (listItems.length === 0) { this.select(listItem); } listItems.push(listItem); }; this.select = function(listItem) { angular.forEach(listItems, function(item) { item.selected = false; }); listItem.selected = true; $rootScope.$broadcast('selectedItemChanged', angular.copy(listItem.item)); }; this.selected = function() { var selectedItem = null; angular.forEach(listItems, function(listItem) { if (listItem.selected) { selectedItem = listItem; } }); return selectedItem; }; this.save = function(item) { this.selected().item = item; }; }], }; }])
選択アイテムの変更時は「selectable-list-item(子)→selectable(親)→selected-content(子)」の順で通知するようにしている。
親から子へ通知する時は、scope.$broadcast
を利用してselectedItemChanged
イベントを発生させるようにした。
scope.$broadcast
はDOMツリーの下位方向へイベントの発生を通知することができる。(逆は$emit
。)
多分親から子へ通知する方法はまだあるのだろうけど、僕はこの方法をよく使う。
今回はsaveボタンを押した時に保存されるようにしたので、選択したアイテムはangular.copy()
を使ってディープコピーしたものを渡している。
selectable
ディレクティブのselected
は公開APIにする必要があまりないのだけど、selected-content
ディレクティブの初期選択が反映されなかったので、そこで使うために仕方なく公開にした。
1番最初にアイテムを追加した時にselectedItemChanged
イベントは発行されるのだけど、この時はまだselected-content
ディレクティブは動作していないということだろうか。
(解決策ご存知の方は教えてください。。)
書籍
- 作者: Brad Green,Shyam Seshadri,牧野聡
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/04/18
- メディア: 大型本
- この商品を含むブログ (2件) を見る
Angularの基本的な部分が、シンプルかつ過不足なくまとまった一冊。隣にあるだけで心強い。