[解決済み] Angular 2.0で動的コンポーネントをコンパイルするために動的テンプレートを使用/作成するにはどうすればよいですか?
質問
テンプレートを動的に作成したい。これを利用して
ComponentType
を実行時に配置し
(置き換えも)
をホスティングしているComponentの中のどこかに入れてください。
RC4までは、私は
ComponentResolver
が、RC5では以下のようなメッセージが表示されます。
ComponentResolver is deprecated for dynamic compilation.
Use ComponentFactoryResolver together with @NgModule/@Component.entryComponents or ANALYZE_FOR_ENTRY_COMPONENTS provider instead.
For runtime compile only, you can also use Compiler.compileComponentSync/Async.
こんなドキュメントがありました( Angular 2 同期ダイナミックコンポーネントの作成 )
そして、私はどちらかを使用することができることを理解します。
-
動的な種類
ngIf
とComponentFactoryResolver
. の中で既知のコンポーネントを渡すと@Component({entryComponents: [comp1, comp2], ...})
- を使うことができますね。.resolveComponentFactory(componentToRender);
-
リアルランタイムコンパイルで
Compiler
...
しかし、問題はそれをどう使うかです
Compiler
? 上の注意書きには、「呼び出す」と書いてあります。
Compiler.compileComponentSync/Async
- で、どうやって?
例えば を作りたい。 (いくつかの設定条件に基づいて) このようなテンプレートは、ある種の設定に対して
<form>
<string-editor
[propertyName]="'code'"
[entity]="entity"
></string-editor>
<string-editor
[propertyName]="'description'"
[entity]="entity"
></string-editor>
...
そして、別のケースでは、このようなものです。
(
string-editor
に置き換えられます。
text-editor
)
<form>
<text-editor
[propertyName]="'code'"
[entity]="entity"
></text-editor>
...
などなど
(異なる番号/日付/参照
editors
プロパティの種類によって、ユーザーによってはいくつかのプロパティをスキップしてしまう...)
つまり、これは一例であり、実際の構成ではもっと異なる複雑なテンプレートが生成される可能性があります。
テンプレートが変更されるため
ComponentFactoryResolver
と既存のものを渡す・・・。を使った解決策が必要です。
Compiler
.
解決方法は?
EDIT - 関連 2.3.0 (2016-12-07)
<ブロッククオート注意:以前のバージョンの解決策を得るには、この投稿の履歴を確認してください。
似たような話題はこちら
Angular 2 の $compile に相当するものです。
. 私たちは
JitCompiler
と
NgModule
. についてもっと読む
NgModule
をAngular2で使用することができます。
ひとことで言うと
があります。
動作するプランカー/サンプル
(ダイナミックテンプレート、ダイナミックコンポーネントタイプ、ダイナミックモジュール。
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
NgModule
を必要とします。
NgModule
s.
非常にシンプルな例を示したいと思いますが、この場合、3つのモジュールが必要になります。 (実際には4つ。ただしAppModuleは数えない) . これを見てください 単純なスニペットではなく をベースに、本当にしっかりとした動的コンポーネントジェネレータを作ることができます。
があります。
一
モジュールは、すべての小さなコンポーネント、例えば
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つ目は、Dynamicなものを扱うためのモジュールになります。これはホスティングコンポーネントといくつかのプロバイダを含みますが、これらはシングルトンになります。そのため、標準的な方法で公開することになります。
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 をご覧ください。
A テンプレート ビルダー
この例では、このような エンティティ
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
. このようなプロパティ(-ies)は、次に作成するダイナミック・コンポーネントの一部である必要があります。
もう少し簡単にするために、テンプレート・ビルダーが使用できるプロパティを定義するインターフェイスを使用することができます。これは、ダイナミックコンポーネントタイプで実装されます。
export interface IHaveDynamicData {
public entity: any;
...
}
A
ComponentFactory
ビルダー
ここで非常に重要なのが、「心得」です。
コンポーネントタイプ、ビルドは
DynamicTypeBuilder
しかし、そのテンプレートが異なるだけです。 (上記で作成した) . コンポーネントのプロパティ (入力、出力、またはいくつかの protected)はそのままです。 異なるプロパティが必要な場合、テンプレートとタイプビルダーの異なる組み合わせを定義する必要があります。
つまり、私たちのソリューションの核心に触れているのです。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でもキャッシュされます。 によって、これらの タイプ . そして、同じテンプレート文字列に対して新しい型を作り直すと、メモリリークが発生するようになります。
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
. 私たちの
ターゲット・プレースホルダー
をインスタンス化するように要求されます。
その
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関連) をご覧ください。 歴史
関連
-
[解決済み] イオン4オブザーバブル
-
[解決済み] ActivatedRouteSnapshot の注入ができない。
-
[解決済み] CLIでコンポーネントを削除する最も良い方法は何ですか?
-
[解決済み] switchの使用時に「ステートメントはif文でフィルタリングされなければならない」というtslintのクレームが発生する。
-
[解決済み] Angular2 Selectorが、ネストしたComponentのどの要素にもマッチしない。
-
[解決済み] NGIf else "の使い方を教えてください。
-
[解決済み] コンポーネントテンプレートで要素を選択するには?
-
[解決済み] Angular 2の "select "で新しい選択範囲を取得するにはどうすればよいですか?
-
[解決済み】AngularでjQueryを使用するには?
-
[解決済み】Angular-CLIでコンポーネントを作成し、特定のモジュールに追加する。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] node_modules が空になったので、`npm install`を実行する必要があるかもしれない。
-
[解決済み] @viewChildが機能しない - プロパティnativeElementがundefinedで読み込めない
-
[解決済み】Angular2エラー。exportAs "が "ngForm "に設定されたディレクティブは存在しません。
-
[解決済み] Angular 2で簡単なアコーディオンを作成するにはどうすればよいですか?
-
[解決済み] チョキダーからのエラー(C:┣ᴗ┣)。Error: EBUSY: resource busy or locked, lstat 'C:\DumpStack.log.tmp.
-
[解決済み] Angular2の$document.ready()に相当します。
-
[解決済み] Angularアプリのシンタックスエラー。予期しないトークン <
-
[解決済み] Angular HTMLバインディング
-
[解決済み】ユーザークリックで選択されたコンポーネントを含むダイナミックタブ
-
[解決済み] Angular 2の$compileに相当するもの