angular.io Guide: Dynamic Component Loader

これは、Angular の[公式ドキュメントの Dynamic Component Loader の章](https://angular.io/guide/dynamic-component-loader) を意訳したものです｡
駆け足で翻訳したので至らない点もありますが、あしからずご了承を｡
バージョン 4.2.6 のドキュメントをベースにしています｡

# Dynamic Component Loader
コンポーネントテンプレートは常に固定されている訳ではありません。アプリケーションは、実行時に新しいコンポーネントをロードする必要があります｡

このクックブックでは、ComponentFactoryResolver を使用してコンポーネントを動的に追加する方法を説明します。 
このクックブックのコードの [ライブサンプル](https://angular.io/generated/live-examples/dynamic-component-loader/eplnkr.html)/[サンプルをダウンロード](https://angular.io/generated/zips/dynamic-component-loader/dynamic-component-loader.zip)で、確認できます｡

## Dynamic component loading
次の例は、動的広告バナーを作成する方法を示しています。

ヒーローエージェンシーは、いくつかの異なる広告が同じバナーエリア内で表示が循環する広告キャンペーンを計画しています。新しい広告コンポーネントは、いくつかの異なるチームによって頻繁に追加されます。この場合、静的なコンポーネント構造を持つテンプレートで実装するのは現実的な方法ではありません。

代わりに、広告バナーのテンプレート内のコンポーネントへ固定参照はせずに、新しく動的にコンポーネントを読み込む方法が必要です｡

Angularには、コンポーネントを動的にロードする独自のAPIが付属しています。

## アンカーディレクティブ

コンポーネントを追加する前に、アンカーポイントを定義して、Angularにコンポーネントを挿入する場所を指定する必要があります。

広告バナーは、AdDirectiveというヘルパーディレクティブを使用して、テンプレート内の有効な挿入ポイントをマーキングします。

*src/app/ad.directive.ts*
```
import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[ad-host]',
})
export class AdDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}
```

`AdDirective` は、`ViewContainerRef` を注入して、動的に追加されたコンポーネントをホストする要素のビューコンテナにアクセスします。

`@Directive` デコレータでは、セレクタ名 `ad-host` に注目してください。これは、要素にディレクティブを適用するために使用します。次のセクションでは、その方法について説明します。

## コンポーネントのローディング
広告バナーの実装のほとんどは、`ad-banner.component.ts` ファイル内にあります。この例では物事を単純にするために、HTMLは `@Component` デコレータの `template` プロパティにテンプレート文字列として直接記述しています。

`<ng-template>` 要素は、直前に作成したディレクティブを適用する場所です。 AdDirective を適用するには、セレクタを ad.directive.ts、`ad-host` から呼び出します。それをブラケット記号`[]`  なしで `<ng-template>` に適用します。
これでAngularはコンポーネントを動的にロードする場所を認識します｡

*src/app/ad-banner.component.ts (template)*

```
template: `
            <div class="ad-banner">
              <h3>Advertisements</h3>
              <ng-template ad-host></ng-template>
            </div>
          `
```

`<ng-template>` 要素は、追加の出力を表示しないため、動的コンポーネントに適しています。

## コンポーネントの解決
`ad-banner.component.ts` のメソッドを詳しく見てみましょう。

`AdBannerComponent` は、`AdItem` オブジェクトの配列を入力として受け取ります。これは最終的に `AdService` から取得されます。 `AdItem` オブジェクトは、読み込むコンポーネントのタイプとコンポーネントにバインドするデータを指定します。

`AdBannerComponent` にコンポーネントの配列を渡すことで、テンプレート内に静的要素のない広告の動的リストを作成できます。

`getAds()` メソッドを使用すると、`AdBannerComponent` は` AdItem` の配列を循環し、 `loadComponent()` を呼び出して 3 秒ごとに新しいコンポーネントを読み込みます。

*src/app/ad-banner.component.ts（抜粋）*

```
export class AdBannerComponent implements AfterViewInit, OnDestroy {
  @Input() ads: AdItem[];
  currentAddIndex: number = -1;
  @ViewChild(AdDirective) adHost: AdDirective;
  subscription: any;
  interval: any;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) { }

  ngAfterViewInit() {
    this.loadComponent();
    this.getAds();
  }

  ngOnDestroy() {
    clearInterval(this.interval);
  }

  loadComponent() {
    this.currentAddIndex = (this.currentAddIndex + 1) % this.ads.length;
    let adItem = this.ads[this.currentAddIndex];

    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);

    let viewContainerRef = this.adHost.viewContainerRef;
    viewContainerRef.clear();

    let componentRef = viewContainerRef.createComponent(componentFactory);
    (<AdComponent>componentRef.instance).data = adItem.data;
  }

  getAds() {
    this.interval = setInterval(() => {
      this.loadComponent();
    }, 3000);
  }
}
```

`loadComponent()` メソッドはここで重くなっています。ステップバイステップで見ていきましょう｡
はじめに、広告を選びます｡

> `loadComponent()` が広告を選択する仕組み
> 
> loadComponent() メソッドは、数式を使用して広告を選択します。
> まず、`currentAddIndex` を 現在の値 +1 に置き換え、それを `AdItem` 配列の長さで割って余りを新しい `currentAddIndex` 値として使用して、`currentAddIndex` を設定します。
> 次に、その値を使用して配列から `adItem` を選択します。

`loadComponent()` は、広告を選択した後、`ComponentFactoryResolver` を使用して、特定のコンポーネントごとに` ComponentFactory` を解決します。
次に、`ComponentFactory` は各コンポーネントのインスタンスを作成します。

次に、コンポーネントのこの特定のインスタンスに存在するviewContainerRefをターゲットにします。この特定のインスタンスがどのようなものか、把握していますか？
これは `adHost` を指しており、`adHost` は、以前に設定した、動的コンポーネントを挿入する場所をAngularに指示するためのディレクティブです。

あなたが思い出しているように、`AdDirective` はそのコンストラクタに `ViewContainerRef` を注入します。これは、ディレクティブが動的コンポーネントをホストするために使用する要素にアクセスする方法です。

コンポーネントをテンプレートに追加するには、 `ViewContainerRef` で `createComponent()` を呼び出します。

### セレクタ参照
一般的に、Angularコンパイラーは、テンプレートで参照されているコンポーネントの `ComponentFactory` を生成します。ただし動的にロードされるコンポーネントのテンプレートには、実行時にロードされるため、セレクタ参照はありません。

コンパイラが引き続きファクトリを生成するようにするには、動的にロードされるコンポーネントを `NgModule` の `entryComponents` 配列に追加します。

*src/app/app.module.ts (entry components)*

```
entryComponents: [ HeroJobAdComponent, HeroProfileComponent ],
```

## AdComponent インターフェース
広告バナーでは、すべてのコンポーネントが共通の `AdComponent` インターフェイスを実装して、APIを標準化してコンポーネントにデータを渡します。

次の2つのサンプルコンポーネントと、参照用の `AdComponent` インターフェイスがあります。

*hero-job-ad.component.ts*

```
import { Component, Input } from '@angular/core';

import { AdComponent }      from './ad.component';

@Component({
  template: `
    <div class="job-ad">
      <h4>{{data.headline}}</h4> 
      
      {{data.body}}
    </div>
  `
})
export class HeroJobAdComponent implements AdComponent {
  @Input() data: any;

}
```

*hero-profile.component.ts*

```
import { Component, Input }  from '@angular/core';

import { AdComponent }       from './ad.component';

@Component({
  template: `
    <div class="hero-profile">
      <h3>Featured Hero Profile</h3>
      <h4>{{data.name}}</h4>
      
      <p>{{data.bio}}</p>

      <strong>Hire this hero today!</strong>
    </div>
  `
})
export class HeroProfileComponent implements AdComponent {
  @Input() data: any;
}
```

*ad.component.ts*

```
export interface AdComponent {
  data: any;
}
```

## 最終的な広告バナー

![Ads](https://angular.io/generated/images/guide/dynamic-component-loader/ads.gif)