Angular で動的にコンポーネントを生成し画面に挿入する
Angular に Compiler#compileModuleAndAllComponentsSync()
というメソッドがあるのを知り、ちょっと遊んでみた。
コレは動的に NgModule とコンポーネントを生成できるシロモノで、「画面から入力された HTML ソースを基に Angular コンポーネントとしてコンパイルする」なんてことまでできてしまった。
以下、app.component.ts
で実装したサンプルソース全量。[(ngModel)]
の使用箇所があるため、app.module.ts
では FormsModule
を imports
しておくこと。
import { CommonModule } from '@angular/common';
import { Compiler, Component, NgModule, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: `
<p><textarea [(ngModel)]="htmlStr" placeholder="ココに HTML を入力してください"></textarea></p>
<p><button (click)="compileHtml(htmlStr)">コンパイル開始</button></p>
<p>コンパイル結果 :</p>
<div style="border: 2px dashed blue; padding: 15px;">
<div #dest></div>
</div>
`
})
export class AppComponent {
/** コンポーネントの実装を受け取るテキストエリアの値 */
public htmlStr: string = '';
/**
* ダイナミックコンテンツを挿入する子コンポーネントの参照を定義しておく
* HTML 側では @ViewChild の第2引数に指定した文字列で <div #dest></div> のように配置する要素を用意しておく
*/
@ViewChild('dest', { read: ViewContainerRef })
public dest: ViewContainerRef;
/**
* コンストラクタ
*
* @param compiler コンパイラ
*/
constructor(private compiler: Compiler) { }
/**
* 引数の文字列からコンポーネントを生成し親コンポーネントに挿入する
*
* @param htmlStr this.htmlStr を渡す
*/
public directInjection(htmlStr: string): void {
// Angular コンポーネントを用意する
@Component({
selector: 'app-temp',
// 「this.htmlStr」とは指定できない。引数で文字列を渡す
template: htmlStr
})
class TempComponent { }
// Angular モジュールを用意する
@NgModule({
imports: [CommonModule],
declarations: [TempComponent],
exports: [TempComponent]
})
class TempModule { }
// 元の要素をクリアする
this.dest.clear();
// Angular モジュールとコンポーネントをコンパイルする
const module = this.compiler.compileModuleAndAllComponentsSync(TempModule);
// Angular モジュールから TempComponent を生成するファクトリを取り出す
const factory = module.componentFactories.find((c) => {
return c.componentType === TempComponent;
});
// TempComponent を生成し this.dest に設定する
this.dest.createComponent(factory);
}
}
こうして出来た AppComponent を見ると、テキストエリアと「コンパイル開始」ボタン、そして初期状態では空の青い枠線が見えるだろう。テキストエリアに
<style>button { font-size; 200%; }</style>
<p style="background: #ff0;">
<button onclick="alert('Test!')">Example</button>
</p>
このような HTML コードを書いて「コンパイル開始」ボタンを押すと、青枠の中に大きな button 要素が表示され、クリックするとダイアログが表示されるだろう。Angular コンポーネントとして正しくコンパイルされている。
実際はユーザから受け取った HTML ソースをコンパイルするようなことは少なく、テンプレート HTML を動的に切り替えるような使い方になるかと。
結局は NgModule と Component なので、TempModule で何かライブラリを imports
しておき、TempComponent に sample(event)
メソッドなどを用意しておけば、<button (click)="sample($event)">ボタン</button>
といったテンプレート HTML をコンパイルして実行させることもできる。
<div #dest></div>
のようにコンポーネント (テンプレート) の出力先を予め実装しておく必要があるので、中々万能ではないが、面白い機能だ。