編集 - 2.3.0に関連(2016-12-07)
注: 以前のバージョンの解決策を得るには、この投稿の履歴を確認してください
同様のトピックがここで議論されています Angular 2 の $compile に相当します。と を使用する必要がJitCompiler
ありNgModule
ます。NgModule
Angular2の詳細については、こちらをご覧ください。
手短に
動作中の plunker/example があります(動的テンプレート、動的コンポーネント タイプ、動的モジュールなどJitCompiler
)。
プリンシパルは次のとおりです
。1)テンプレートの作成
2)ComponentFactory
キャッシュ内の検索- 7)に移動
3) - Component
4) の作成 - Module
5) の作成 - コンパイルModule
6) - 戻り (および後で使用するためにキャッシュ) ComponentFactory
7)ターゲットの使用およびComponentFactory
インスタンスの作成動的のComponent
ここにコード スニペットがあります(詳細はこちら) - カスタム ビルダーは、ビルド/キャッシュされたばかりを返しComponentFactory
、ビュー ターゲット プレースホルダーは、のインスタンスを作成するために消費します。DynamicComponent
// here we get a TEMPLATE with dynamic content === TODO
var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);
// here we get Factory (just compiled or from cache)
this.typeBuilder
.createComponentFactory(template)
.then((factory: ComponentFactory<IHaveDynamicData>) =>
{
// Target will instantiate and inject component (we'll keep reference to it)
this.componentRef = this
.dynamicComponentTarget
.createComponent(factory);
// let's inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
これはそれです - 一言で言えばそれです。詳細を取得するには..以下をお読みください
.
TL&DR
プランカーを観察し、一部のスニペットでさらに説明が必要な場合に備えて、戻って詳細を読みます
.
詳細説明 - Angular2 RC6++ &ランタイム コンポーネント
このシナリオの説明の下で、
- モジュール
PartsModule:NgModule
(小片のホルダー)を作成します
DynamicModule:NgModule
動的コンポーネントを含む別のモジュールを作成します(およびPartsModule
動的に参照します)
- 動的テンプレートを作成する(単純なアプローチ)
- 新しい
Component
タイプを作成する(テンプレートが変更された場合のみ)
- 新しいを作成します
RuntimeModule:NgModule
。このモジュールには、以前に作成したComponent
タイプが含まれます
- 電話
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
してもらうComponentFactory
DynamicComponent
ビュー ターゲット プレースホルダーの - ジョブのインスタンスを作成し、ComponentFactory
- 新しいインスタンスに割り当てる(
@Inputs
から編集に切り替える) 、消費する INPUT
TEXTAREA
@Outputs
Ngモジュール
sが必要NgModule
です。
非常に単純な例を示したいと思いますが、この場合、3 つのモジュールが必要になります(実際には 4 つですが、AppModule は数えません)。本当に堅実な動的コンポーネント ジェネレーターの基礎として、単純なスニペットではなく、これを使用してください。
、( 、...)など、すべての小さなコンポーネントに対して1 つのモジュールがあります。string-editor
text-editor
date-editor
number-editor
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
拡張DYNAMIC_DIRECTIVES
可能で、動的コンポーネント テンプレート/タイプに使用されるすべての小さなパーツを保持するためのものです。app/parts/parts.module.ts を確認してください
2 つ目は、動的スタッフ処理のモジュールです。これには、ホスティング コンポーネントといくつかのプロバイダーが含まれます。シングルトンになります。そのため、標準的な方法で公開します-forRoot()
import { DynamicDetail } from './detail.view';
import { DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';
@NgModule({
imports: [ PartsModule ],
declarations: [ DynamicDetail ],
exports: [ DynamicDetail],
})
export class DynamicModule {
static forRoot()
{
return {
ngModule: DynamicModule,
providers: [ // singletons accross the whole app
DynamicTemplateBuilder,
DynamicTypeBuilder
],
};
}
}
の使用方法を確認してforRoot()
くださいAppModule
DynamicTypeBuilder
最後に、アドホックなランタイム モジュールが必要になりますが、これはジョブの一部として後で作成されます。
4 番目のモジュールであるアプリケーション モジュールは、宣言コンパイラ プロバイダーを保持するモジュールです。
...
import { COMPILER_PROVIDERS } from '@angular/compiler';
import { AppComponent } from './app.component';
import { DynamicModule } from './dynamic/dynamic.module';
@NgModule({
imports: [
BrowserModule,
DynamicModule.forRoot() // singletons
],
declarations: [ AppComponent],
providers: [
COMPILER_PROVIDERS // this is an app singleton declaration
],
そこでNgModuleについてもっと読んでください(読んでください):
テンプレートビルダー_
この例では、この種のエンティティの詳細を処理します
entity = {
code: "ABC123",
description: "A description of this Entity"
};
を作成するtemplate
には、このplunkerでこのシンプル/ナイーブ ビルダーを使用します。
真のソリューションである真のテンプレート ビルダーは、アプリケーションが多くのことを実行できる場所です。
// plunker - app/dynamic/template.builder.ts
import {Injectable} from "@angular/core";
@Injectable()
export class DynamicTemplateBuilder {
public prepareTemplate(entity: any, useTextarea: boolean){
let properties = Object.keys(entity);
let template = "<form >";
let editorName = useTextarea
? "text-editor"
: "string-editor";
properties.forEach((propertyName) =>{
template += `
<${editorName}
[propertyName]="'${propertyName}'"
[entity]="entity"
></${editorName}>`;
});
return template + "</form>";
}
}
ここでのトリックは、既知のプロパティのセットを使用するテンプレートを作成することentity
です。このようなプロパティ (-ies) は、次に作成する動的コンポーネントの一部である必要があります。
もう少し簡単にするために、インターフェイスを使用して、テンプレート ビルダーが使用できるプロパティを定義できます。これは、動的コンポーネント タイプによって実装されます。
export interface IHaveDynamicData {
public entity: any;
...
}
ComponentFactory
ビルダー_
ここで非常に重要なことは、次のことに注意してください。
コンポーネント タイプである build with ourは異なる可能性がありますが、そのテンプレート(上記で作成)DynamicTypeBuilder
のみが異なります。コンポーネントのプロパティ(入力、出力、または一部の保護) は同じです。別のプロパティが必要な場合は、テンプレートとタイプ ビルダーの別の組み合わせを定義する必要があります。
したがって、私たちはソリューションの核心に触れています。Builder は、1) 作成ComponentType
2) 作成NgModule
3) コンパイルComponentFactory
4)後で再利用するためにキャッシュします。
受け取る必要がある依存関係:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
そして、ここに取得する方法のスニペットがありますComponentFactory
:
// plunker - app/dynamic/type.builder.ts
// this object is singleton - so we can use this as a cache
private _cacheOfFactories:
{[templateKey: string]: ComponentFactory<IHaveDynamicData>} = {};
public createComponentFactory(template: string)
: Promise<ComponentFactory<IHaveDynamicData>> {
let factory = this._cacheOfFactories[template];
if (factory) {
console.log("Module and Type are returned from cache")
return new Promise((resolve) => {
resolve(factory);
});
}
// unknown template ... let's create a Type for it
let type = this.createNewComponent(template);
let module = this.createComponentModule(type);
return new Promise((resolve) => {
this.compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories) =>
{
factory = _.find(moduleWithFactories.componentFactories
, { componentType: type });
this._cacheOfFactories[template] = factory;
resolve(factory);
});
});
}
上記では、 との両方を作成してキャッシュします。テンプレート(実際には、そのすべての実際の動的部分)が同じである場合..再利用できるためです。Component
Module
そして、実行時に装飾されたクラス/型を作成するための本当にクールな方法を表す 2 つのメソッドがあります。だけでなく@Component
、@NgModule
protected createNewComponent (tmpl:string) {
@Component({
selector: 'dynamic-component',
template: tmpl,
})
class CustomDynamicComponent implements IHaveDynamicData {
@Input() public entity: any;
};
// a component for this particular template
return CustomDynamicComponent;
}
protected createComponentModule (componentType: any) {
@NgModule({
imports: [
PartsModule, // there are 'text-editor', 'string-editor'...
],
declarations: [
componentType
],
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
重要:
コンポーネントの動的タイプは異なりますが、テンプレートが異なるだけです。そのため、その事実を使用してそれらをキャッシュします。これは本当にとても重要です。Angular2 はこれらもキャッシュします.. typeによって。同じテンプレート文字列の新しい型を再作成すると、メモリ リークが発生し始めます。
ComponentFactory
ホスティング コンポーネントによって使用される
最後のピースは、動的コンポーネントのターゲットをホストするコンポーネント<div #dynamicContentPlaceHolder></div>
です。それへの参照を取得し、それを使用ComponentFactory
してコンポーネントを作成します。それは一言で言えば、ここにそのコンポーネントのすべての部分があります(必要に応じて、ここでプランカーを開きます)
最初に import ステートメントを要約しましょう。
import {Component, ComponentRef,ViewChild,ViewContainerRef} from '@angular/core';
import {AfterViewInit,OnInit,OnDestroy,OnChanges,SimpleChange} from '@angular/core';
import { IHaveDynamicData, DynamicTypeBuilder } from './type.builder';
import { DynamicTemplateBuilder } from './template.builder';
@Component({
selector: 'dynamic-detail',
template: `
<div>
check/uncheck to use INPUT vs TEXTAREA:
<input type="checkbox" #val (click)="refreshContent(val.checked)" /><hr />
<div #dynamicContentPlaceHolder></div> <hr />
entity: <pre>{{entity | json}}</pre>
</div>
`,
})
export class DynamicDetail implements AfterViewInit, OnChanges, OnDestroy, OnInit
{
// wee need Dynamic component builder
constructor(
protected typeBuilder: DynamicTypeBuilder,
protected templateBuilder: DynamicTemplateBuilder
) {}
...
テンプレートとコンポーネントビルダーを受け取るだけです。次は、この例に必要なプロパティです(詳細はコメントで)
// reference for a <div> with #dynamicContentPlaceHolder
@ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef})
protected dynamicComponentTarget: ViewContainerRef;
// this will be reference to dynamic content - to be able to destroy it
protected componentRef: ComponentRef<IHaveDynamicData>;
// until ngAfterViewInit, we cannot start (firstly) to process dynamic stuff
protected wasViewInitialized = false;
// example entity ... to be recieved from other app parts
// this is kind of candiate for @Input
protected entity = {
code: "ABC123",
description: "A description of this Entity"
};
この単純なシナリオでは、ホスティング コンポーネントには@Input
. したがって、変更に対応する必要はありません。しかし、その事実にもかかわらず(そして今後の変更に備えるために) 、コンポーネントがすでに(最初に)開始されている場合は、フラグを導入する必要があります。そうして初めて、魔法を始めることができます。
最後に、コンポーネント ビルダーを使用し、そのコンパイル済み/キャッシュ済みの ComponentFacotry
. Target プレースホルダーは、そのファクトリでをインスタンス化するComponent
よう求められます。
protected refreshContent(useTextarea: boolean = false){
if (this.componentRef) {
this.componentRef.destroy();
}
// here we get a TEMPLATE with dynamic content === TODO
var template = this.templateBuilder.prepareTemplate(this.entity, useTextarea);
// here we get Factory (just compiled or from cache)
this.typeBuilder
.createComponentFactory(template)
.then((factory: ComponentFactory<IHaveDynamicData>) =>
{
// Target will instantiate and inject component (we'll keep reference to it)
this.componentRef = this
.dynamicComponentTarget
.createComponent(factory);
// let's inject @Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
}
小さな延長
destroy()
また、コンパイルされたテンプレートへの参照を保持する必要があります..それを変更するたびに適切にできるようにする必要があります。
// this is the best moment where to start to process dynamic stuff
public ngAfterViewInit(): void
{
this.wasViewInitialized = true;
this.refreshContent();
}
// wasViewInitialized is an IMPORTANT switch
// when this component would have its own changing @Input()
// - then we have to wait till view is intialized - first OnChange is too soon
public ngOnChanges(changes: {[key: string]: SimpleChange}): void
{
if (this.wasViewInitialized) {
return;
}
this.refreshContent();
}
public ngOnDestroy(){
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
終わり
それはほとんどそれです。動的に構築されたものを破棄することを忘れないでください(ngOnDestroy)。また、動的にキャッシュし、唯一の違いがテンプレートである場合は必ずキャッシュしてください。types
modules
ここですべての動作を確認してください
この投稿の以前のバージョン(RC5 関連など)を確認するには、履歴を確認してください