module.component  - AngularJS v1.5

[AngularJS Advent Calendar 2015](http://qiita.com/advent-calendar/2015/angularjs) 6日目の投稿です。

先日 AngularJS v1.5.0-beta.2 がリリースされましたね。
そこで、v1.5.x で新しく追加された機能３つを、今日から3日連続で紹介してみます。

## module.component
AngularJS v1.5 から `module.component` という機能が実装されました。
これまでは  `<my-element></my-element>` など独自要素を作成する際は、`module.directive` を利用していました。しかしv1.5 からは `module.component` を利用することで、より少ないコードで実装することができるようになります。

## とりあえず使ってみる
[参考サイト](http://toddmotto.com/exploring-the-angular-1-5-component-method/)を見ながら試しに使ってみます。以下のような構文になります。

```js
angular.module('sampleApp', [])
.controller(...) // 初期値とか設定
}]);

angular.module('sampleApp')
.component('counter', {
  bindings: {
    count: '='
  },
  controller: function () {
    function increment() {
      this.count++;
    }
    function decrement() {
      this.count--;
    }
    this.increment = increment;
    this.decrement = decrement;
  },
  template: [
    '<div class="todo">',
      '<input type="text" ng-model="counter.count">',
      '<button type="button" ng-click="counter.decrement();">-</button>',
      '<button type="button" ng-click="counter.increment();">+</button>',
    '</div>'
  ].join('')
});
```

<img width="200" alt="counter.png" src="https://qiita-image-store.s3.amazonaws.com/0/17439/87f53ce4-1b85-24ee-35c0-8df576c1493e.png">
※出来上がりの図

## コードを見てみる
実際にAngularJS v1.5.0-beta.2 での実装をみてみます。
[loader.js ] (https://github.com/angular/angular.js/blob/54e816552f20e198e14f849cdb2379fed8570c1a/src/loader.js#L362-L396)に記述されているとのこと。

```js
component: function(name, options) {
            function factory($injector) {
              function makeInjectable(fn) {
                if (angular.isFunction(fn)) {
                  return function(tElement, tAttrs) {
                    return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
                  };
                } else {
                  return fn;
                }
              }

              var template = (!options.template && !options.templateUrl ? '' : options.template);
              return {
                controller: options.controller || function() {},
                controllerAs: identifierForController(options.controller) || options.controllerAs || name,
                template: makeInjectable(template),
                templateUrl: makeInjectable(options.templateUrl),
                transclude: options.transclude === undefined ? true : options.transclude,
                scope: options.isolate === false ? true : {},
                bindToController: options.bindings || {},
                restrict: options.restrict || 'E'
              };
            }

            if (options.$canActivate) {
              factory.$canActivate = options.$canActivate;
            }
            if (options.$routeConfig) {
              factory.$routeConfig = options.$routeConfig;
            }
            factory.$inject = ['$injector'];

            return moduleInstance.directive(name, factory);
          },
```

`module.component` に渡せる options のプロパティを見ると、`module.directive` と比べて少ないですね。 `controllerAs` や `transclude` 、 `scope ` などの定義が簡素化されてます。
[公式ブログ](http://angularjs.blogspot.jp/2015/11/angularjs-15-beta2-and-14-releases.html) で "`module.component` は独自要素を作成するための特別な directive である"という通り、処理の最後に`moduleInstance.directive`( = `module.directive`) に設定値を渡しているのがわかります。

## module.directive との違い

### 構文がちょっとだけシンプルに。

```js
// ディレクティブは function を渡す
module.directive('counter', function counter() {
  return { someProperties: xxx };
});
```

```js
// コンポーネントは object を渡す
module.component('counter', {
  someProperties: xxx;
});
```

### link プロパティが設定できない
`module.directive` で利用していた `link` プロパティは渡すことができなさそう。
個人的には `link` はややこしいと感じていたため、スッキリするので大歓迎。
大抵のUIコンポーネントはこれで作れば問題ないです。逆に、 `module.directive` でないと実現できないようなものについては、今まで通り `module.directive` を使うことになりそう（相当複雑なやつじゃないと出番はないかも）です。

### bindings プロパティ
module.directive で定義していた `scope` と `bindToController` は、この `bindings` にまとめて定義する形になりました。こちらもちょっとスッキリ。

### transclude も、ちょっとシンプルに。
`module.directive` では、`transclude` と  `replace` の関係でエラーが出たりしてましたが、これがちょこっとシンプルになります。`module.directive`　では `replace` が設定できないため `false` として設定されます。
あとこの `transclude` が複数箇所に指定できるようになったんですが、それは後日紹介します。

### scope プロパティはデフォルトで isolate scope に。
`isolate` プロパティは true / false で指定することで、`module.directive` の `scope: {} || true` 相当になります。`module.directive` の `scope: false` 相当の設定はできません。

### module.directive がなくせるかというと...
必ずしもそうではなさそう。上記の諸々の制約があるため、複雑なコンポーネントを作る場合はやっぱり directive をつかって作りこむ必要があるのではないかなと。ボク自身そんな複雑なコンポーネントを作ろうと思わないので、かなり限定された状況のみで directive を使うのかなーっていう印象です。

## v2.x への準備
[参考サイト](http://toddmotto.com/exploring-the-angular-1-5-component-method/)の最後らへんに、v2.x に migration しやすいよってあります。

最初に紹介したカウンターのサンプルを v2.x で書くと以下のようになるとのこと。

* `controller` を `Class` に変更
* `selector` を追記
* `template` を v2.x の構文に変更

```js
var Counter = ng
.Component({
  selector: 'counter',
  template: [
    '<div class="todo">',
      '<input type="text" [(ng-model)]="count">',
      '<button type="button" (click)="decrement();">-</button>',
      '<button type="button" (click)="increment();">+</button>',
    '</div>'
  ].join('')
})
.Class({
  constructor: function () {
    this.count = 0;
  },
  increment: function () {
    this.count++;
  },
  decrement: function () {
    this.count--;
  }
});
```

確かに既存の `module.direcive` の `link` でごにょごにょしてるのを、上のような構文に移すのは確かに手間だなーとは思う。なので、できる限り近しい状態にできるようにしておくと少しだけ楽ができるのかもしれない。


## まとめ
`module.component` のおかげで、より単純にUIコンポーネントを作成できるようになりました。
v2.x に向けての準備もできるので率先して使っていくとよさそう。
まちがってたら教えてください。

### 参考
* [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)
* [http://toddmotto.com/exploring-the-angular-1-5-component-method/](http://toddmotto.com/exploring-the-angular-1-5-component-method/)