Multi-slot transclusion - AngularJS v1.5

[AngularJS Advent Calendar 2015](http://qiita.com/advent-calendar/2015/angularjs) 7日目の投稿です。
v1.5 で追加された Multi-slot transclusion についてです。

## Multi-slot transclusion
従来の AngularJS にも transclusion をする仕組みはありました。それは、`directive` を作る時に利用していた `transclude`プロパティ です。ただし、1つの `directive` で transclusion をおこなうことができるのは1箇所だけでした。
`Multi-slot transclusion` では、その transclusion を複数箇所に対しておこなえるようにするための仕組みです。それが v1.5 から実装されました。先日書いた  `module.component`でも利用可能です。


## とりあえず見てみる
以下のような構文です。[Demoはこちらから。](http://plnkr.co/edit/73qiILR5JRrg0NBlqw10?p=preview)

```html
<autocomplete-widget>
  <input type="text" ng-model="app.searchCriteria" placeholder="Search..." />
  
  <results>
    <div ng-repeat="item in app.getResults(app.searchCriteria) as results">
      {{ item }}
    </div>
    <div ng-if="results.length==0">
      No results found
    </div>
  </results>	
</autocomplete-widget>
```
上記のように、 `<autocomplete-widget>`タグの中に `<input>`と`<results>`が書かれています。
この2つの要素が、`autocomplete-widget` の `template` に transclusion されます。

```html
<!-- autocomplete-widget-tpl.html -->
<div class="autocomplete-container">
  <div class="search-area">
    <div class="search-icon">
      <i class="fa fa-search"></i>
    </div>
    <div class="search-input" ng-transclude="inputArea"></div>
  </div>
  <div class="results-area">
    <div ng-transclude="resultsArea"></div>
  </div>
</div>
```
こちらが `autocomplete-widget` の `template` の実装です。
`ng-transclude="inputArea"` と `ng-transclude="resultsArea"` となっているところが、今回追加された機能となります。


```js
angular.module('MyApplication', [])
  .controller('ApplicationController', function() {
    ... // スコープの初期化
  })
  .component('autocompleteWidget', {
    templateUrl: 'autocomplete-widget-tpl.html',
    transclude: {
      input: 'inputArea',
      results: 'resultsArea'
    }
  });
```

`transclude`プロパティに transclusion 元のタグ（`derective名含む`）と `ng-transclude="xxx"` の属性値で、どこに何を配置するかを決定してます。


## コードを見てみる
[ngTransclude.js](https://github.com/angular/angular.js/blob/master/src/ng/directive/ngTransclude.js#L161-L193)にあります。

```js
var ngTranscludeMinErr = minErr('ngTransclude');
var ngTranscludeDirective = ngDirective({
  restrict: 'EAC',
  link: function($scope, $element, $attrs, controller, $transclude) {

    if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
      // If the attribute is of the form: `ng-transclude="ng-transclude"`
      // then treat it like the default
      $attrs.ngTransclude = '';
    }

    function ngTranscludeCloneAttachFn(clone) {
      if (clone.length) {
        $element.empty();
        $element.append(clone);
      }
    }

    if (!$transclude) {
      throw ngTranscludeMinErr('orphan',
       'Illegal use of ngTransclude directive in the template! ' +
       'No parent directive that requires a transclusion found. ' +
       'Element: {0}',
       startingTag($element));
    }

    // If there is no slot name defined or the slot name is not optional
    // then transclude the slot
    var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;
    $transclude(ngTranscludeCloneAttachFn, null, slotName);
  }
});
```

最後で渡している `slotName` が、今回追加された設定です。（`$transclude` は深くて追いきれなかったので断念...）

## Fallback
[API Doc](https://docs.angularjs.org/api/ng/directive/ngTransclude)の最後の方にもサンプルが書かれていて、ここを見ると `Fallback ` 機能が書かれているんですけど、なんかうまくいきませんでした。（ドキュメント自体が古い？）

## おまけ
[API Doc](https://docs.angularjs.org/api/ng/directive/ngTransclude)ですが、なぜかデフォルトが v1.5.0-build.x になっているのでお気をつけください。この状態でサンプルへのリンクを踏んでもエラーを出力している状態になってしまいます。
お試ししたい場合は、 v1.5.0-beta.x の API Doc になっているか確認してからみてください。
<img width="312" alt="スクリーンショット 2015-12-08 0.02.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/17439/070acc6e-fd30-f166-1794-e693ebfe2b80.png">


## まとめ
これは結構、活躍する場面が出てくると思います。便利！
ただ、親子関係のコンポーネントを作る場合は、`module.component` より `module.directve` で作ったほうが、後々幸せになれそう（細い設定ができるため）
まだ Fallback の挙動なのか仕様なのかがはっきりしていないのはβだからってことにしておこう（この投稿参考に
ならないかも？）


## 参考サイト
[http://angularjs.blogspot.jp/2015/11/angularjs-15-beta2-and-14-releases.html](http://angularjs.blogspot.jp/2015/11/angularjs-15-beta2-and-14-releases.html)
[https://docs.angularjs.org/api/ng/directive/ngTransclude](https://docs.angularjs.org/api/ng/directive/ngTransclude)