AngularJS Directive なんてこわくない(その3)


replace, transclude, scope

前々回前回に引き続き、今回もカスタム directive について。

今回のサンプルコードは、UI Bootstrap の Alert からで、replace transclude scopeオプションについて見ていく。

alert.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
angular.module("ui.bootstrap.alert", []).directive('alert', function() {
  return {
    restrict:'EA',
    templateUrl:'template/alert/alert.html',
    transclude:true,
    replace:true,
    scope: {
      type: '=',
      close: '&'
    },
    link: function(scope, iElement, iAttrs) {
      scope.closeable = "close" in iAttrs;
    }
  };
});
template/alert/alert.html
1
2
3
4
<div class="alert" ng-class="type && 'alert-' + type">
    <button ng-show="closeable" type="button" class="close" ng-click="close()">&times;</button>
    <div ng-transclude></div>
</div>
1
2
3
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">
  {{alert.msg}}
</alert>

replace

templateまたはtemplateUrlで指定された HTML のフラグメントは、デフォルトでは directive を指定した要素の内側に append される。

1
2
3
4
5
<alert ...>
  <div class='alert' ...>
    ...
  </div>
</alert>

このコード例のように、directive を要素として指定できるようrestrict'E'を含めるときには、HTML として不適当な要素名が記述されることになるため、replace: trueを指定する前提で directive を設計しよう。

replace: true を指定することによって、directive を指定した要素自体を置き換えることができるので、directive がコンパイルされた結果は以下のようなコードとなる。

1
2
3
4
<div ng-class="type && 'alert-' + type" class="alert ng-scope" close="closeAlert($index)" type="alert.type" ng-repeat="alert in alerts">
    <button ng-click="close()" class="close" type="button" ng-show="closeable">×</button>
    <div ng-transclude=""><span class="ng-scope ng-binding">Another alert!</span></div>
</div>

replace: true の場合、 directive を指定した要素にある属性は、テンプレート側のルート要素にコピーされる。なので、ng-repeat="alert in alerts"type="alert.type"close="closeAlert($index)"も有効となる。

両方に class 属性があるケースでは、両方の class 属性値をいい感じにマージしてくれる。

transclude

transcludeには、trueまたは'element'を指定でき、directive とした要素の内容を、テンプレートの一部として利用できる。

1
2
transclude: true // directive 要素の内容(内側)をテンプレートで利用
transclude: 'element' // directive 要素ごとテンプレートで利用

テンプレート側に ng-transclude を指定した要素の内側に append できる。上記サンプルコードの例では、{{alert.msg}}<div ng-transclude>の内側に append される。

上記コード例では、alertdirective の内側部分{{alert.msg}}が、ng-transcludeに append されていることがわかる(alert.msg: 'Another alert!'という前提)。

scope

scope には、以下の3種類の指定方法がある。

1
2
3
scope: false // directive が利用される場所での scope を利用(デフォルト)
scope: true // directive が利用される場所での scope を継承する、新たな scope を生成
scope: {...} // directive が利用される場所での scope を継承しない、独立した新たな scope を生成

scope: trueが指定されている代表的な directive はng-controllerで、ご存知のとおりng-controllerを利用すると scope を継承する新たな scope が生成される。これにより、親の scope にあるデータや function を利用したり、オーバーライドしたりできるようになる。

一方で、コンポーネントとして再利用可能な directive を設計するには、directive を利用する場所での scope による影響を受けない分離・独立した scope を生成したい。

1
2
3
4
5
scope: {
  name: '@', // interpolate(値、string)
  info: '=', // data bind
  cancel: '&' // expression(function)
}

このように、scopeにオブジェクトを記述することで、この directive が利用されるたびにname info cancelのみ存在する新たな scope が生成される。

なお、directive を利用する側と、テンプレート側とで異なる名前を使いたいときは、以下のように記述することができる。

1
2
3
scope: {
  customerInfo: '=info'
}

こうすることで、テンプレート内ではcustomerInfoを、directive を利用する要素での属性名にはinfoを利用できるようになる。

4回目へ向けて

この3回目で、再利用のための directive を記述する方法が理解できた。もう Directive なんてこわくない!

なんだけど、4回目でも引き続き、controller requireといった directive のプロパティを扱う。