EDIT -に関連2.3.0(2016年12月7日)
注:以前のバージョンの解決策を取得するには、この投稿の履歴を確認してください
同様のトピックがここで説明されています。Angular2の$ compileに相当します。とを使用する必要がJitCompiler
ありNgModule
ます。NgModule
Angular2の詳細については、こちらをご覧ください。
一言で言えば
ある作業plunker /例 (動的テンプレート、動的コンポーネントタイプ、動的モジュールは、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
- のインスタンスを作成する-View
DynamicComponent
TargetプレースホルダーのジョブとComponentFactory
- 割り当て
@Inputs
に新しいインスタンス (からスイッチINPUT
にTEXTAREA
編集)、消費@Outputs
NgModule
NgModule
s が必要です。
非常に単純な例を示したいのですが、この場合、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 - 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
です。そのようなプロパティは、次に作成する動的コンポーネントの一部である必要があります。
もう少し簡単にするために、テンプレートビルダーで使用できるインターフェイスを使用してプロパティを定義できます。これは、動的コンポーネントタイプによって実装されます。
export interface IHaveDynamicData {
public entity: any;
...
}
ComponentFactory
ビルダー
ここで非常に重要なことは、覚えておくことです。
で構築するコンポーネントタイプDynamicTypeBuilder
は異なる可能性がありますが、そのテンプレート(上記で作成)のみが異なります。コンポーネントのプロパティ(入力、出力、または一部の保護)は同じです。異なるプロパティが必要な場合は、テンプレートとタイプビルダーの異なる組み合わせを定義する必要があります
したがって、私たちはソリューションのコアに触れています。ビルダーは、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もキャッシュしますで。これらのタイプ。同じテンプレート文字列に対して新しいタイプを再作成すると、メモリリークが発生し始めます。
ComponentFactory
ホスティングコンポーネントで使用
最後のピースはコンポーネントで、動的コンポーネントのターゲットをホストします<div #dynamicContentPlaceHolder></div>
。それへの参照を取得ComponentFactory
し、コンポーネントの作成に使用します。一言で言えば、ここにそのコンポーネントのすべての部分があります(必要に応じて、ここでプランカーを開きます)
最初にインポート文を要約しましょう:
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
。私たちの目標プレースホルダは、インスタンス化することが求められますその工場で。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関連など)を表示するには、履歴を確認してください