web-dev-qa-db-ja.com

どうやって動的テンプレートを使って動的コンポーネントをコンパイルすることができますか? Angular 2.0?

templatedynamic作成したいです。これはRuntimeに​​ComponentTypename__を構築し、ホスティングコンポーネントの中のどこかに(replace)を配置するために使用されるべきです。

RC4まではComponentResolvername__を使用していましたが、RC5では次のようなメッセージが表示されます。

ComponentResolvername__は動的コンパイルでは非推奨です。代わりに@NgModule/@Component.entryComponentsまたはANALYZE_FOR_ENTRY_COMPONENTSプロバイダーと共にComponentFactoryResolvername__を使用してください。 ランタイムコンパイル専用の場合は、Compiler.compileComponentSync/Asyncも使用できます。

私はこの(公式角度2)の文書を見つけた

Angular 2同期動的コンポーネント作成

そして私がどちらかを使用できることを理解

  • ngIfname__を持つ動的なComponentFactoryResolvername__の種類。既知のコンポーネントを@Component({entryComponents: [comp1, comp2], ...})内のホストに渡す場合 - 私は.resolveComponentFactory(componentToRender);を使うことができます
  • Compilername __...を使用した実際のランタイムコンパイル.

しかし、問題はそのCompilerNAME_の使い方です。上記のメモは、Compiler.compileComponentSync/Asyncを呼び出す必要があることを示しています。

例えば。私は(いくつかの設定条件に基づく) 1種類の設定のためのこの種のテンプレートを作りたい

<form>
   <string-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></string-editor>
   <string-editor
     [propertyName]="'description'"
     [entity]="entity"
   ></string-editor>
   ...

また別のケースではこれはstring-editortext-editor)に置き換えられます

<form>
   <text-editor
     [propertyName]="'code'"
     [entity]="entity"
   ></text-editor>
   ...

そのため、(プロパティの種類によって番号/日付/参照のeditorsname__が異なります。一部のユーザーは一部のプロパティをスキップしました...)}のようになります。すなわちこれは一例です。実際の設定では、はるかに多様で複雑なテンプレートを生成できます。

テンプレートがを変更しているので、ComponentFactoryResolvername__を使用して既存のものを渡すことはできません... Compilername__による解決策が必要です


AOTとJitCompiler (以前のRuntimeCompiler)

AOTでこの機能を使用しますか(事前コンパイル)。あなたは得ていますか:

エラー:シンボル値を静的に解決する際にエラーが発生しました。関数呼び出しはサポートされていません。 .../node_modules/@ angular/compiler/src/compiler.d.tsのシンボルCOMPILER_PROVIDERSを解決して、関数またはラムダをエクスポートされた関数への参照(元の.tsファイルの65:17の位置)に置き換えることを検討してください。

コメントを残して、ここに投票してください。

COMPILER_PROVIDERSを使用したコードをAOTでサポートできますか。

172
Radim Köhler

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 に住んでください。

50
Rene Hamburger

私は遅くパーティーに到着したにちがいない、ここでの解決策のどれも私にとって有用であるように思われなかった - あまりにも面倒でそしてあまりにも多くの回避策のように感じた。

私がやってしまったのは、Angular 4.0.0-beta.6ngComponentOutlet を使うことです。

これにより、動的コンポーネントのファイルに書かれている最短で最も単純な解決策が得られました。

  • これは単純にテキストを受け取りそれをテンプレートに入れる簡単な例ですが、明らかにあなたはあなたの必要性に従って変えることができます:
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;
  }
}
  • 簡単な説明:
    1. my-component - 動的コンポーネントがレンダリングしているコンポーネント
    2. DynamicComponent - 動的に構築され、my-componentの内部にレンダリングされるコンポーネント

すべてのAngularライブラリを^ Angular 4.0.0にアップグレードすることを忘れないでください

幸運を祈っています。

_ update _

角度5でも機能します。

46
Ophir Stern

学んだことすべてを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 {}`
15
Stephen Paul

角度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

8
Long Field

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  {
}

Stackblitz

7
Stephen Paul

ng-dynamicdynamicComponent ディレクティブを使用することで、これをAngular 2最終バージョンで解決しました。

使用法:

<div *dynamicComponent="template; context: {text: text};"></div>

Templateがあなたの動的テンプレートであるところで、コンテキストはあなたがあなたのテンプレートをバインドしたい動的データモデルに設定することができます。

5
Richard Houltz

Radminの素晴らしい答えをフォローして、Angular-Cliバージョン1.0.0-beta.22以降を使用しているすべての人に必要なちょっとしたTweakがあります。

COMPILER_PROVIDERS インポートできなくなりました (詳細は angular-cli GitHub を参照)。

そのための回避策は、JitCompilerセクションでCOMPILER_PROVIDERSprovidersをまったく使用しないことですが、代わりに型ビルダークラス内で次のように '@ angular/compiler'のJitCompilerFactoryを使用します。

private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();

お分かりのように、それは注射不可能であり、したがってDIとの依存関係はありません。この解決方法は、Angular-Cliを使用していないプロジェクトにも有効です。

4
Sebastian

Radimによるこの非常に優れた記事の上にいくつかの詳細を追加したいと思います。

私はこの解決策を取り、少しの間それに取り組んだがすぐにいくつかの制限に遭遇した。それらの概要を説明してから、その解決策も示します。

  • まず第一に、私はダイナミックディテールの中にダイナミックディテールをレンダリングすることができませんでした(基本的には互いの中に動的UIをネストします)。
  • 次の問題は、ソリューションで利用可能になった部分の1つの中に動的詳細をレンダリングしたいということでした。それは最初の解決策でも不可能でした。
  • 最後に、文字列エディタのような動的部分でテンプレートURLを使用することは不可能でした。

私はこの記事に基づいて、これらの制限をどのように達成するかについて別の質問をしました。

Angular 2での再帰的動的テンプレートコンパイル /

私がこれらの制限に対する答えを概説するだけです、あなたが私と同じ問題に出くわすならば、それは解決策をかなり柔軟にするからです。初期の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.tsdynamic.module.tsを変更する必要があります。

parts.module.tsの内側DYNAMIC_DIRECTIVESDynamicDetailを追加する必要があります。

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);
            });
    });

これが他の誰かに役立つことを願っています!

敬具モーテン

4

角度7.xでは、これに角度要素を使用しました。

  1. @ angular-elements npmをインストールします。i @ angular/elements -s

  2. 付属サービスを作成します。

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"を使用しました。

  1. その後、AppComponentでregisterを呼び出す必要があります。
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
  1. そして今、あなたのコードのどこでもあなたはこのようにそれを使うことができます:
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を使用してオブジェクトを渡す必要があります。その後、もう一度解析します。もっと良い解決策が見つかりません。

4
Oleg Pnk

私は自分で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]式の "["には意味があり、これは一方向のデータバインディングを示します。したがって、プロパティを変数にバインドする必要がないことがわかっている場合は、それらを省略してもかまいません。

2
zii

これは、サーバーから生成された動的フォームコントロールの例です。

https://stackblitz.com/edit/angular-t3mmg6

この例は動的なフォームコントロールがaddコンポーネント内にある場合です(これはサーバーからフォームコントロールを取得できる場所です)。 addcomponentメソッドが表示されたら、フォームコントロールを見ることができます。この例では、角度のあるマテリアルを使用していませんが、動作します(@ workを使用しています)。これはAngular 6をターゲットにしていますが、以前のバージョンではすべて動作します。

AngularVersion 5以降にはJITComplierFactoryを追加する必要があります。

ありがとう

ビジェイ

1

この特定のケースでは、コンポーネントを動的に作成するためのディレクティブを使用することがより良い選択肢になるでしょう。例:

コンポーネントを作成したい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は同じで、あなたのメタデータを定義することができます。

あなたの設定や入力タイプに応じて、ディレクティブはそれに応じてそしてサポートされたタイプから動作するべきです、それは適切なコンポーネントをレンダリングするでしょう。そうでない場合はエラーを記録します。

0
saidutt