templateをdynamic作成したいです。これはRuntimeにComponentType
name__を構築し、ホスティングコンポーネントの中のどこかに(replace)を配置するために使用されるべきです。
RC4まではComponentResolver
name__を使用していましたが、RC5では次のようなメッセージが表示されます。
ComponentResolver
name__は動的コンパイルでは非推奨です。代わりに@NgModule/@Component.entryComponents
またはANALYZE_FOR_ENTRY_COMPONENTSプロバイダーと共にComponentFactoryResolver
name__を使用してください。 ランタイムコンパイル専用の場合は、Compiler.compileComponentSync/Async
も使用できます。
私はこの(公式角度2)の文書を見つけた
そして私がどちらかを使用できることを理解
ngIf
name__を持つ動的なComponentFactoryResolver
name__の種類。既知のコンポーネントを@Component({entryComponents: [comp1, comp2], ...})
内のホストに渡す場合 - 私は.resolveComponentFactory(componentToRender);
を使うことができますCompiler
name __...を使用した実際のランタイムコンパイル.しかし、問題はそのCompiler
NAME_の使い方です。上記のメモは、Compiler.compileComponentSync/Async
を呼び出す必要があることを示しています。
例えば。私は(いくつかの設定条件に基づく) 1種類の設定のためのこの種のテンプレートを作りたい
<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
name__が異なります。一部のユーザーは一部のプロパティをスキップしました...)}のようになります。すなわちこれは一例です。実際の設定では、はるかに多様で複雑なテンプレートを生成できます。
テンプレートがを変更しているので、ComponentFactoryResolver
name__を使用して既存のものを渡すことはできません... Compiler
name__による解決策が必要です
AOTでこの機能を使用しますか(事前コンパイル)。あなたは得ていますか:
エラー:シンボル値を静的に解決する際にエラーが発生しました。関数呼び出しはサポートされていません。 .../node_modules/@ angular/compiler/src/compiler.d.tsのシンボルCOMPILER_PROVIDERSを解決して、関数またはラムダをエクスポートされた関数への参照(元の.tsファイルの65:17の位置)に置き換えることを検討してください。
コメントを残して、ここに投票してください。
EDIT(26/08/2017) :以下の解決法はAngular 2と4でうまくいきます。テンプレート変数とクリックハンドラを含むように更新し、Angular 4.3でテストしました。
Angular 4では、 Ophir's answer で説明されているngComponentOutletが、はるかに優れた解決策です。しかし今はまだ{ 入力と出力をサポートしていません _です。 [this PR]( https://github.com/angular/angular/pull/15362] が受け入れられた場合、createイベントによって返されたコンポーネントインスタンスを通じて可能になります。
ng-dynamic-component は完全に最善かつ最も単純な解決策かもしれませんが、私はまだそれをテストしていません。
@ Long Fieldの回答が注目されています!これは別の(同期)例です。
import {Compiler, Component, NgModule, OnInit, ViewChild,
ViewContainerRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
@Component({
selector: 'my-app',
template: `<h1>Dynamic template:</h1>
<div #container></div>`
})
export class App implements OnInit {
@ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
constructor(private compiler: Compiler) {}
ngOnInit() {
this.addComponent(
`<h4 (click)="increaseCounter()">
Click to increase: {{counter}}
`enter code here` </h4>`,
{
counter: 1,
increaseCounter: function () {
this.counter++;
}
}
);
}
private addComponent(template: string, properties?: any = {}) {
@Component({template})
class TemplateComponent {}
@NgModule({declarations: [TemplateComponent]})
class TemplateModule {}
const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = mod.componentFactories.find((comp) =>
comp.componentType === TemplateComponent
);
const component = this.container.createComponent(factory);
Object.assign(component.instance, properties);
// If properties are changed at a later stage, the change detection
// may need to be triggered manually:
// component.changeDetectorRef.detectChanges();
}
}
@NgModule({
imports: [ BrowserModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
http://plnkr.co/edit/fdP9Oc に住んでください。
私は遅くパーティーに到着したにちがいない、ここでの解決策のどれも私にとって有用であるように思われなかった - あまりにも面倒でそしてあまりにも多くの回避策のように感じた。
私がやってしまったのは、Angular 4.0.0-beta.6
の ngComponentOutlet を使うことです。
これにより、動的コンポーネントのファイルに書かれている最短で最も単純な解決策が得られました。
import {
Component, OnInit, Input, NgModule, NgModuleFactory, Compiler
} from '@angular/core';
@Component({
selector: 'my-component',
template: `<ng-container *ngComponentOutlet="dynamicComponent;
ngModuleFactory: dynamicModule;"></ng-container>`,
styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
dynamicComponent;
dynamicModule: NgModuleFactory<any>;
@Input()
text: string;
constructor(private compiler: Compiler) {
}
ngOnInit() {
this.dynamicComponent = this.createNewComponent(this.text);
this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
}
protected createComponentModule (componentType: any) {
@NgModule({
imports: [],
declarations: [
componentType
],
entryComponents: [componentType]
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
protected createNewComponent (text:string) {
let template = `dynamically created template with text: ${text}`;
@Component({
selector: 'dynamic-component',
template: template
})
class DynamicComponent implements OnInit{
text: any;
ngOnInit() {
this.text = text;
}
}
return DynamicComponent;
}
}
my-component
- 動的コンポーネントがレンダリングしているコンポーネントDynamicComponent
- 動的に構築され、my-componentの内部にレンダリングされるコンポーネントすべてのAngularライブラリを^ Angular 4.0.0にアップグレードすることを忘れないでください
幸運を祈っています。
_ update _
角度5でも機能します。
学んだことすべてを1つのファイルに圧縮することにしました 。特にRC5以前に比べて、ここで取り入れるべきことはたくさんあります。このソースファイルにはAppModuleとAppComponentが含まれています。
import {
Component, Input, ReflectiveInjector, ViewContainerRef, Compiler, NgModule, ModuleWithComponentFactories,
OnInit, ViewChild
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
@Component({
selector: 'app-dynamic',
template: '<h4>Dynamic Components</h4><br>'
})
export class DynamicComponentRenderer implements OnInit {
factory: ModuleWithComponentFactories<DynamicModule>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }
ngOnInit() {
if (!this.factory) {
const dynamicComponents = {
sayName1: {comp: SayNameComponent, inputs: {name: 'Andrew Wiles'}},
sayAge1: {comp: SayAgeComponent, inputs: {age: 30}},
sayName2: {comp: SayNameComponent, inputs: {name: 'Richard Taylor'}},
sayAge2: {comp: SayAgeComponent, inputs: {age: 25}}};
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
.then((moduleWithComponentFactories: ModuleWithComponentFactories<DynamicModule>) => {
this.factory = moduleWithComponentFactories;
Object.keys(dynamicComponents).forEach(k => {
this.add(dynamicComponents[k]);
})
});
}
}
addNewName(value: string) {
this.add({comp: SayNameComponent, inputs: {name: value}})
}
addNewAge(value: number) {
this.add({comp: SayAgeComponent, inputs: {age: value}})
}
add(comp: any) {
const compFactory = this.factory.componentFactories.find(x => x.componentType === comp.comp);
// If we don't want to hold a reference to the component type, we can also say: const compFactory = this.factory.componentFactories.find(x => x.selector === 'my-component-selector');
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
const cmpRef = this.vcRef.createComponent(compFactory, this.vcRef.length, injector, []);
Object.keys(comp.inputs).forEach(i => cmpRef.instance[i] = comp.inputs[i]);
}
}
@Component({
selector: 'app-age',
template: '<div>My age is {{age}}!</div>'
})
class SayAgeComponent {
@Input() public age: number;
};
@Component({
selector: 'app-name',
template: '<div>My name is {{name}}!</div>'
})
class SayNameComponent {
@Input() public name: string;
};
@NgModule({
imports: [BrowserModule],
declarations: [SayAgeComponent, SayNameComponent]
})
class DynamicModule {}
@Component({
selector: 'app-root',
template: `
<h3>{{message}}</h3>
<app-dynamic #ad></app-dynamic>
<br>
<input #name type="text" placeholder="name">
<button (click)="ad.addNewName(name.value)">Add Name</button>
<br>
<input #age type="number" placeholder="age">
<button (click)="ad.addNewAge(age.value)">Add Age</button>
`,
})
export class AppComponent {
message = 'this is app component';
@ViewChild(DynamicComponentRenderer) dcr;
}
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent, DynamicComponentRenderer],
bootstrap: [AppComponent]
})
export class AppModule {}`
角度2 rc 6の動的成分の使い方を示す簡単な例を示します。
たとえば、動的HTML template = template1があり、動的ロードを行いたい、まずコンポーネントにラップするとします。
@Component({template: template1})
class DynamicComponent {}
ここではtemplate1をhtmlとして、ng2 componentを含めることができます。
Rc6以降、@ NgModuleにこのコンポーネントをラップさせる必要があります。 @NgModuleは、anglarJS 1のモジュールと同じように、ng2アプリケーションのさまざまな部分を分離しているので、次のようになります。
@Component({
template: template1,
})
class DynamicComponent {
}
@NgModule({
imports: [BrowserModule,RouterModule],
declarations: [DynamicComponent]
})
class DynamicModule { }
(ここで私の例のようにRouterModuleをインポートすると、後で見ることができるように、いくつかのルートコンポーネントが私のhtmlにあります)
これで、DynamicModuleを次のようにコンパイルできます。this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))
それを読み込むには、app.moudule.tsに上記を追加する必要があります。私のapp.moudle.tsを参照してください。より完全な詳細についてはチェックしてください: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts およびapp.moudle.ts
そしてデモを参照してください: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview
2019年2月の答え
素晴らしいニュース! @ angular/cdk パッケージは、 ポータル のファーストクラスをサポートしているようです。
1)あなたのモジュールで:
@NgModule({
imports: [ ..., PortalModule, ... ],
entryComponents: [ChildComponent]
})
export class AppModule { }
2)あなたのコンポーネントで:
import { Component, OnInit } from '@angular/core';
import { ComponentPortal } from '@angular/cdk/portal';
@Component({
selector: 'my-app',
template: `
<h3>Portal Example</h3>
<button (click)="onClickAddChild()">Click to add component dynamically</button>
<ng-template [cdkPortalOutlet]="myPortal"></ng-template>
`
})
export class AppComponent {
myPortal;
onClickAddChild() {
this.myPortal = new ComponentPortal(ChildComponent);
}
}
@Component({
selector: 'my-child',
template: `<p>I am a dynamic component!</p>`
})
export class ChildComponent {
}
ng-dynamic の dynamicComponent ディレクティブを使用することで、これをAngular 2最終バージョンで解決しました。
使用法:
<div *dynamicComponent="template; context: {text: text};"></div>
Templateがあなたの動的テンプレートであるところで、コンテキストはあなたがあなたのテンプレートをバインドしたい動的データモデルに設定することができます。
Radminの素晴らしい答えをフォローして、Angular-Cliバージョン1.0.0-beta.22以降を使用しているすべての人に必要なちょっとしたTweakがあります。
COMPILER_PROVIDERS
インポートできなくなりました (詳細は angular-cli GitHub を参照)。
そのための回避策は、JitCompiler
セクションでCOMPILER_PROVIDERS
とproviders
をまったく使用しないことですが、代わりに型ビルダークラス内で次のように '@ angular/compiler'のJitCompilerFactory
を使用します。
private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();
お分かりのように、それは注射不可能であり、したがってDIとの依存関係はありません。この解決方法は、Angular-Cliを使用していないプロジェクトにも有効です。
Radimによるこの非常に優れた記事の上にいくつかの詳細を追加したいと思います。
私はこの解決策を取り、少しの間それに取り組んだがすぐにいくつかの制限に遭遇した。それらの概要を説明してから、その解決策も示します。
私はこの記事に基づいて、これらの制限をどのように達成するかについて別の質問をしました。
私がこれらの制限に対する答えを概説するだけです、あなたが私と同じ問題に出くわすならば、それは解決策をかなり柔軟にするからです。初期のplunkerもそれで更新するのは素晴らしいことです。
動的ディテールを入れ子にするには、 type.builder.ts のimport文にDynamicModule.forRoot()を追加する必要があります。
protected createComponentModule (componentType: any) {
@NgModule({
imports: [
PartsModule,
DynamicModule.forRoot() //this line here
],
declarations: [
componentType
],
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
それに加えて、<dynamic-detail>
を文字列エディタまたはテキストエディタである部分の1つの中で使用することは不可能でした。
それを可能にするためにはparts.module.ts
とdynamic.module.ts
を変更する必要があります。
parts.module.ts
の内側DYNAMIC_DIRECTIVES
にDynamicDetail
を追加する必要があります。
export const DYNAMIC_DIRECTIVES = [
forwardRef(() => StringEditor),
forwardRef(() => TextEditor),
DynamicDetail
];
dynamic.module.ts
でもdynamicDetailを削除する必要があります
@NgModule({
imports: [ PartsModule ],
exports: [ PartsModule],
})
実用的な修正されたplunkerはここで見つけることができます: http://plnkr.co/edit/UYnQHF?p=preview (私はこの問題を解決しなかった、私はただメッセンジャーです:-D)
最後に、動的コンポーネント上に作成されたパーツにtemplateurlを使用することは不可能でした。解決策(または回避策。それがバグであるのか、それともフレームワークを誤って使用しているのか私はよくわかりません)。それを注入するのではなく、コンストラクター内でコンパイラーを作成することでした。
private _compiler;
constructor(protected compiler: RuntimeCompiler) {
const compilerFactory : CompilerFactory =
platformBrowserDynamic().injector.get(CompilerFactory);
this._compiler = compilerFactory.createCompiler([]);
}
それから_compiler
を使ってコンパイルすると、templateUrlsも有効になります。
return new Promise((resolve) => {
this._compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories) =>
{
let _ = window["_"];
factory = _.find(moduleWithFactories.componentFactories, { componentType: type });
this._cacheOfFactories[template] = factory;
resolve(factory);
});
});
これが他の誰かに役立つことを願っています!
敬具モーテン
角度7.xでは、これに角度要素を使用しました。
@ angular-elements npmをインストールします。i @ angular/elements -s
付属サービスを作成します。
import { Injectable, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { IStringAnyMap } from 'src/app/core/models';
import { AppUserIconComponent } from 'src/app/shared';
const COMPONENTS = {
'user-icon': AppUserIconComponent
};
@Injectable({
providedIn: 'root'
})
export class DynamicComponentsService {
constructor(private injector: Injector) {
}
public register(): void {
Object.entries(COMPONENTS).forEach(([key, component]: [string, any]) => {
const CustomElement = createCustomElement(component, { injector: this.injector });
customElements.define(key, CustomElement);
});
}
public create(tagName: string, data: IStringAnyMap = {}): HTMLElement {
const customEl = document.createElement(tagName);
Object.entries(data).forEach(([key, value]: [string, any]) => {
customEl[key] = value;
});
return customEl;
}
}
カスタム要素タグは、角度成分セレクタとは異なる必要があります。 AppUserIconComponentの場合:
...
selector: app-user-icon
...
この場合、カスタムタグ名は "user-icon"を使用しました。
@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(
dynamicComponents: DynamicComponentsService,
) {
dynamicComponents.register();
}
}
dynamicComponents.create('user-icon', {user:{...}});
またはこのように:
const html = `<div class="wrapper"><user-icon class="user-icon" user='${JSON.stringify(rec.user)}'></user-icon></div>`;
this.content = this.domSanitizer.bypassSecurityTrustHtml(html);
(テンプレート内):
<div class="comment-item d-flex" [innerHTML]="content"></div>
後者の場合は、JSON.stringifyを使用してオブジェクトを渡す必要があります。その後、もう一度解析します。もっと良い解決策が見つかりません。
私は自分でRC4をRC5に更新する方法を考えようとしているので、このエントリにつまずいたし、動的コンポーネント作成への新しいアプローチはまだ少し謎が残っているので、コンポーネントファクトリリゾルバについては何も勧めません。
しかし、私が提案できるのは、このシナリオでコンポーネントを作成するためのもう少し明確なアプローチです。テンプレートのswitchを使用して、以下のように、状況に応じて文字列エディタまたはテキストエディタを作成します。
<form [ngSwitch]="useTextarea">
<string-editor *ngSwitchCase="false" propertyName="'code'"
[entity]="entity"></string-editor>
<text-editor *ngSwitchCase="true" propertyName="'code'"
[entity]="entity"></text-editor>
</form>
ちなみに、[prop]式の "["には意味があり、これは一方向のデータバインディングを示します。したがって、プロパティを変数にバインドする必要がないことがわかっている場合は、それらを省略してもかまいません。
これは、サーバーから生成された動的フォームコントロールの例です。
https://stackblitz.com/edit/angular-t3mmg6
この例は動的なフォームコントロールがaddコンポーネント内にある場合です(これはサーバーからフォームコントロールを取得できる場所です)。 addcomponentメソッドが表示されたら、フォームコントロールを見ることができます。この例では、角度のあるマテリアルを使用していませんが、動作します(@ workを使用しています)。これはAngular 6をターゲットにしていますが、以前のバージョンではすべて動作します。
AngularVersion 5以降にはJITComplierFactoryを追加する必要があります。
ありがとう
ビジェイ
この特定のケースでは、コンポーネントを動的に作成するためのディレクティブを使用することがより良い選択肢になるでしょう。例:
コンポーネントを作成したいHTML内
<ng-container dynamicComponentDirective [someConfig]="someConfig"></ng-container>
私は次のようにして指令にアプローチし設計します。
const components: {[type: string]: Type<YourConfig>} = {
text : TextEditorComponent,
numeric: NumericComponent,
string: StringEditorComponent,
date: DateComponent,
........
.........
};
@Directive({
selector: '[dynamicComponentDirective]'
})
export class DynamicComponentDirective implements YourConfig, OnChanges, OnInit {
@Input() yourConfig: Define your config here //;
component: ComponentRef<YourConfig>;
constructor(
private resolver: ComponentFactoryResolver,
private container: ViewContainerRef
) {}
ngOnChanges() {
if (this.component) {
this.component.instance.config = this.config;
// config is your config, what evermeta data you want to pass to the component created.
}
}
ngOnInit() {
if (!components[this.config.type]) {
const supportedTypes = Object.keys(components).join(', ');
console.error(`Trying to use an unsupported type ${this.config.type} Supported types: ${supportedTypes}`);
}
const component = this.resolver.resolveComponentFactory<yourConfig>(components[this.config.type]);
this.component = this.container.createComponent(component);
this.component.instance.config = this.config;
}
}
テキスト、文字列、日付、その他何でも - HTMLでng-container
要素に渡してきたどんな設定でも利用できます。
設定yourConfig
は同じで、あなたのメタデータを定義することができます。
あなたの設定や入力タイプに応じて、ディレクティブはそれに応じてそしてサポートされたタイプから動作するべきです、それは適切なコンポーネントをレンダリングするでしょう。そうでない場合はエラーを記録します。