Transclude内からディレクティブにアクセスする

2016-07-21

AngularJS1.5を使っていて、transcludeで中に要素を埋め込むようなコンポーネントで、その埋め込まれる要素内からコンポーネントが提供する機能にアクセスする、ということをしたかった。例えばダイアログ的な枠組みを提供するコンポーネントでtranscludeで内容を埋め込めて、その中のボタンからダイアログを閉じる、みたいな。

しかしtranscludeされる要素は枠組みのコンポーネントとは別の孤立したスコープになってしまい、コンポーネントにはアクセスできないようだった。アクセスさせるにはディレクティブのlinktranscludeの制御をする必要があるようだ。

利用側

// Application
angular.module('app')
.component('app', {
template: (
'<h1>App</h1>' +
'<my-dialog>' +
' <div style="border:1px solid blue; margin:4px">' +
' <h3>Transcluded contents</h3>' +
' <button ng-click="closeDialog()">ok</button>' +
' </div>' +
'</my-dialog>' +
'<h2>Outside of dialog</h2>' +
'<button ng-click="closeDialog()">not worked (as expected)</button>'
),
})
  • my-dialog がダイアログを提供するディレクティブだとして、その中にレイアウトを記述したものが埋め込まれる、という想定
  • transcludeされる要素内からダイアログが提供するcloseDialog()という関数を呼び出して、ディレクティブ側に通知する
  • transclude外からはその機能は呼べない

ディレクティブ側

// Directive using transclude
angular.module('app')
.directive('myDialog', function() {
return {
scope: {}, // ディレクティブが独立したスコープを持つようにする
link: function(scope, element, _attrs, _controller, transcludeFn) {
scope.message = '.'
// トランスクルードする要素を生成
transcludeFn(scope.$parent.$new(), function(clone, transcludeScope) {
var count = 0
transcludeScope.closeDialog = function() {
scope.message = 'Dialog closed: ' + (++count)
}
// クローンした要素を埋め込む
var root = element[0].querySelector('#transclude-point')
root.innerHTML = ''
angular.forEach(clone, e => {root.appendChild(e)})
})
},
template: (
'<div style="border:1px solid black">' +
' <h2>Dialog</h2>' +
' <div ng-bind="message"></div>' +
' <div id="transclude-point"></div>' + // ng-transcludeを使う代わりに埋め込むポイントを探せるようにしておく
'</div>'
),
transclude: true,
}
})
  • angularcomponentだとスコープやコントローラにアクセスする方法がわからなかったので、directiveとして作成
  • transclude: trueでトランスクルードを有効にする
  • template内でng-transcludeを使って埋め込むと、孤立したスコープでtransclude要素が埋め込まれてしまってアクセスする方法がないため、link関数の第5引数のtranscludeFnを使用してtranscludeする要素をクローンして自前で目的の場所に埋め込む
    • その際のスコープはディレクティブのスコープの親から$newして、ディレクティブとは別のスコープにしておく
    • その新しいスコープに提供したい関数(closeDialog)を定義してやることでtransclude内から参照できるようになる

デモ

http://plnkr.co/edit/ADbo0K?p=preview

その他

  • スコープは廃止される、ということなのであまりよくない方法かも…
  • 試してないけどAngular2の場合には外側のコンポーネントから@ViewChildで内側のコンポーネントのコントローラが取得できるので、それ経由でアクセスすればそさそう

リンク