LoginSignup
6
3

More than 5 years have passed since last update.

angular.io Guide: Dynamic Component Loader

Last updated at Posted at 2017-07-09

これは、Angular の公式ドキュメントの Dynamic Component Loader の章 を意訳したものです。
駆け足で翻訳したので至らない点もありますが、あしからずご了承を。
バージョン 4.2.6 のドキュメントをベースにしています。

Dynamic Component Loader

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

このクックブックでは、ComponentFactoryResolver を使用してコンポーネントを動的に追加する方法を説明します。
このクックブックのコードの ライブサンプル/サンプルをダウンロードで、確認できます。

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() メソッドを使用すると、AdBannerComponentAdItem の配列を循環し、 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 を注入します。これは、ディレクティブが動的コンポーネントをホストするために使用する要素にアクセスする方法です。

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

セレクタ参照

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

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

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

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3