これは、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()
メソッドを使用すると、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;
}