Bagaimana saya bisa menggunakan / membuat template dinamis untuk mengkompilasi Komponen dinamis dengan Angular 2.0?

197

Saya ingin membuat templat secara dinamis. Ini harus digunakan untuk membangun ComponentTypesaat runtime dan menempatkan (bahkan mengganti) di suatu tempat di dalam Komponen hosting.

Sampai RC4 saya gunakan ComponentResolver, tetapi dengan RC5 saya mendapatkan pesan berikut:

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.

Saya menemukan dokumen ini ( Angular 2 Synchronous Dynamic Component Creation )

Dan mengerti bahwa saya dapat menggunakan keduanya

  • Agak dinamis ngIfdengan ComponentFactoryResolver. Jika saya melewati komponen yang dikenal di dalam@Component({entryComponents: [comp1, comp2], ...}) - Saya dapat menggunakan.resolveComponentFactory(componentToRender);
  • Kompilasi runtime nyata, dengan Compiler...

Tetapi pertanyaannya adalah bagaimana menggunakannya Compiler? Catatan di atas mengatakan bahwa saya harus menelepon:Compiler.compileComponentSync/Async - jadi bagaimana caranya?

Sebagai contoh. Saya ingin membuat (berdasarkan beberapa kondisi konfigurasi) jenis template ini untuk satu jenis pengaturan

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

dan dalam kasus lain yang ini ( string-editordiganti dengan text-editor)

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

Demikian seterusnya (nomor / tanggal / referensi berbedaeditors berdasarkan tipe properti, melewatkan beberapa properti untuk beberapa pengguna ...) . yaitu ini adalah contoh, konfigurasi sebenarnya dapat menghasilkan templat yang jauh lebih berbeda dan kompleks.

Template berubah, jadi saya tidak bisa menggunakan ComponentFactoryResolverdan meneruskan yang sudah ada ... Saya butuh solusi dengan Compiler.

Radim Köhler
sumber
Karena solusi yang saya temukan sangat baik, saya ingin semua orang menemukan pertanyaan ini untuk melihat jawaban saya yang sangat jauh di bagian paling bawah saat ini. :)
Richard Houltz
Artikel di sini adalah apa yang perlu Anda ketahui tentang komponen dinamis di Angular memiliki penjelasan yang bagus tentang komponen dinamis.
Max Koretskyi
Inilah masalah dengan setiap jawaban di luar sana dan apa yang $compilesebenarnya bisa dilakukan metode ini tidak bisa - Saya membuat aplikasi di mana saya hanya ingin mengkompilasi HTML ketika masuk melalui halaman pihak ke-3 dan panggilan ajax. Saya tidak dapat menghapus HTML dari halaman dan menempatkannya di templat saya sendiri. Sigh
Augie Gardner
@AugieGardner Ada alasan mengapa ini tidak dimungkinkan oleh desain. Angular tidak bersalah atas keputusan arsitektur yang buruk atau sistem warisan yang dimiliki sebagian orang. Jika Anda ingin menguraikan kode HTML yang ada, Anda bebas menggunakan kerangka kerja lain karena Angular berfungsi dengan baik dengan komponen WebComponents. Menetapkan batas yang jelas untuk memandu gerombolan programmer yang tidak berpengalaman lebih penting daripada membiarkan peretasan kotor untuk beberapa sistem lama.
Phil

Jawaban:

163

EDIT - terkait dengan 2.3.0 (2016-12-07)

CATATAN: untuk mendapatkan solusi untuk versi sebelumnya, periksa riwayat posting ini

Topik serupa didiskusikan di sini Setara dengan $ compile di Angular 2 . Kita perlu menggunakan JitCompilerdan NgModule. Baca lebih lanjut tentang NgModuledi Angular2 di sini:

Pendeknya

Ada plunker / contoh yang berfungsi (template dinamis, tipe komponen dinamis, modul dinamis,JitCompiler ,, ... beraksi)

Prinsipnya adalah:
1) buat Template
2) temukan ComponentFactorydi cache - pergi ke 7)
3) - buat Component
4) - buat Module
5) - kompilasi Module
6) - kembali (dan cache untuk digunakan nanti) ComponentFactory
7) gunakan Target danComponentFactory untuk membuat Instance dinamisComponent

Berikut ini cuplikan kode (selengkapnya di sini ) - Pembuat kustom kami kembali hanya dibangun / di-cache ComponentFactorydan tampilan yang dikonsumsi placeholder target untuk membuat turunan dariDynamicComponent

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

Ini dia - singkatnya. Untuk mendapatkan detail lebih lanjut .. baca di bawah ini

.

TL&DR

Amati plunker dan kembali untuk membaca detail seandainya beberapa cuplikan membutuhkan lebih banyak penjelasan

.

Penjelasan terperinci - Angular2 RC6 ++ & komponen runtime

Di bawah ini deskripsi skenario ini , kami akan

  1. buat modul PartsModule:NgModule (tempat potongan kecil)
  2. buat modul lain DynamicModule:NgModule, yang akan berisi komponen dinamis kami (dan referensiPartsModule secara dinamis)
  3. buat Template dinamis (pendekatan sederhana)
  4. buat Componenttipe baru (hanya jika templat telah berubah)
  5. membuat baru RuntimeModule:NgModule. Modul ini akan berisi Componentjenis yang dibuat sebelumnya
  6. telepon JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)untuk mendapatkanComponentFactory
  7. membuat Mesin Virtual dari DynamicComponent- pekerjaan placeholder View Target danComponentFactory
  8. menetapkan @Inputske contoh baru (beralih dari INPUTke TEXTAREAediting) , mengkonsumsi@Outputs

NgModule

Kami membutuhkan sebuah NgModule.

Walaupun saya ingin menunjukkan contoh yang sangat sederhana, dalam hal ini, saya akan membutuhkan tiga modul (sebenarnya 4 - tetapi saya tidak menghitung AppModule) . Tolong, ambil ini daripada potongan sederhana sebagai dasar untuk generator komponen dinamis yang sangat solid.

Akan ada satu modul untuk semua komponen kecil, misalnya string-editor, text-editor ( date-editor, number-editor...)

@NgModule({
  imports:      [ 
      CommonModule,
      FormsModule
  ],
  declarations: [
      DYNAMIC_DIRECTIVES
  ],
  exports: [
      DYNAMIC_DIRECTIVES,
      CommonModule,
      FormsModule
  ]
})
export class PartsModule { }

Di mana DYNAMIC_DIRECTIVESextensible dan dimaksudkan untuk menampung semua bagian kecil yang digunakan untuk templat / tipe Komponen dinamis kami. Periksa app / parts / parts.module.ts

Yang kedua akan menjadi modul untuk penanganan barang Dinamis kami. Ini akan berisi komponen hosting dan beberapa penyedia .. yang akan menjadi lajang. Untuk itu kami akan mempublikasikannya dengan cara standar - denganforRoot()

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
            ], 
        };
    }
}

Periksa penggunaan forRoot()diAppModule

Akhirnya, kita membutuhkan modul adhoc, runtime .. tetapi itu akan dibuat nanti, sebagai bagian dari DynamicTypeBuilder pekerjaan.

Modul keempat, modul aplikasi, adalah orang yang terus menyatakan penyedia kompiler:

...
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
  ],

Baca (baca) lebih banyak tentang NgModule di sana:

Sebuah Template builder

Dalam contoh kita, kita akan memproses detail entitas semacam ini

entity = { 
    code: "ABC123",
    description: "A description of this Entity" 
};

Untuk membuat template, di plunker ini kami menggunakan pembangun sederhana / naif ini.

Solusi nyata, pembuat template nyata, adalah tempat di mana aplikasi Anda dapat melakukan banyak hal

// 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>";
    }
}

Trik di sini adalah - itu membangun template yang menggunakan beberapa set properti yang diketahui, misalnya entity . Properti seperti itu (-ies) harus menjadi bagian dari komponen dinamis, yang akan kita buat selanjutnya.

Untuk membuatnya lebih mudah, kita bisa menggunakan antarmuka untuk mendefinisikan properti, yang bisa digunakan oleh pembuat Template. Ini akan diterapkan oleh tipe Komponen dinamis kami.

export interface IHaveDynamicData { 
    public entity: any;
    ...
}

Seorang ComponentFactorypembangun

Hal yang sangat penting di sini adalah untuk diingat:

tipe komponen kita, build with our DynamicTypeBuilder, bisa berbeda - tetapi hanya dengan templatnya (dibuat di atas) . Properti komponen (input, output atau beberapa yang dilindungi) masih sama. Jika kita membutuhkan properti yang berbeda, kita harus mendefinisikan kombinasi yang berbeda dari Templat dan Pembuat Tipe

Jadi, kami menyentuh inti dari solusi kami. Builder, akan 1) membuat ComponentType2) membuat NgModule3) kompilasi ComponentFactory4) cache itu untuk digunakan kembali nanti.

Ketergantungan yang perlu kami terima:

// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
    
@Injectable()
export class DynamicTypeBuilder {

  // wee need Dynamic component builder
  constructor(
    protected compiler: JitCompiler
  ) {}

Dan di sini adalah cuplikan cara mendapatkan 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);
            });
    });
}

Di atas kita buat dan cache keduanya Componentdan Module. Karena jika templat (sebenarnya bagian dinamis yang sebenarnya dari semua itu) adalah sama .. kita dapat menggunakan kembali

Dan berikut adalah dua metode, yang mewakili cara yang sangat keren cara membuat kelas / tipe yang didekorasi dalam runtime. Tidak hanya @Componenttetapi juga@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;
}

Penting:

tipe dinamis komponen kami berbeda, tetapi hanya berdasarkan templat. Jadi kami menggunakan fakta itu untuk menyimpannya . Ini sangat penting. Angular2 juga akan melakukan cache ini .. berdasarkan jenisnya . Dan jika kita akan membuat ulang untuk string template yang sama tipe baru ... kita akan mulai menghasilkan kebocoran memori.

ComponentFactory digunakan oleh komponen hosting

Bagian terakhir adalah komponen, yang menampung target untuk komponen dinamis kami, mis <div #dynamicContentPlaceHolder></div>. Kami mendapatkan referensi untuk itu dan digunakan ComponentFactoryuntuk membuat komponen. Singkatnya, dan ini semua bagian dari komponen itu (jika perlu, buka plunker di sini )

Mari pertama-tama meringkas pernyataan impor:

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

Kami baru saja menerima, pembuat template dan komponen. Berikutnya adalah properti yang diperlukan untuk contoh kita (lebih banyak di komentar)

// 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" 
  };

Dalam skenario sederhana ini, komponen hosting kami tidak memilikinya @Input. Jadi itu tidak harus bereaksi terhadap perubahan. Namun terlepas dari kenyataan itu (dan harus siap untuk perubahan yang akan datang) - kita perlu memperkenalkan beberapa flag jika komponen sudah (pertama) diinisiasi. Dan hanya dengan begitu kita bisa memulai keajaiban.

Akhirnya kita akan menggunakan pembangun komponen kita, dan itu baru dikompilasi / di-cache ComponentFacotry . Kami placeholder Sasaran akan diminta untuk instantiate yangComponent dengan pabrik itu.

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

ekstensi kecil

Juga, kita perlu menyimpan referensi untuk template yang dikompilasi .. untuk dapat melakukannya dengan benar destroy(), setiap kali kita akan mengubahnya.

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

selesai

Cukup banyak. Jangan lupa Hancurkan apa pun yang dibangun secara dinamis (ngOnDestroy) . Juga, pastikan untuk melakukan cache dinamis typesdan modulesjika satu-satunya perbedaan adalah template mereka.

Periksa semuanya dalam aksi di sini

untuk melihat versi sebelumnya (misalnya terkait RC5) dari posting ini, periksa sejarahnya

Radim Köhler
sumber
50
ini terlihat seperti solusi yang rumit, yang sudah usang sangat sederhana dan jelas, apakah ada cara lain untuk melakukan ini?
tibbus
3
Saya pikir cara yang sama dengan @tibbus: ini mendapat cara yang lebih rumit daripada dulu dengan kode usang. Terima kasih atas jawaban Anda.
Lucio Mollinedo
5
@ribsi terima kasih atas catatan Anda. Biarkan saya mengklarifikasi sesuatu. Banyak jawaban lain mencoba membuatnya sederhana . Tapi saya mencoba menjelaskannya dan menunjukkannya dalam skenario, tertutup untuk penggunaan nyata . Kita perlu untuk men-cache hal-hal, kita harus memanggil menghancurkan pada penciptaan ulang dll. Jadi, sementara keajaiban bangunan dinamis benar-benar type.builder.tsseperti yang Anda tunjukkan, saya berharap, bahwa setiap pengguna akan mengerti bagaimana menempatkan semua itu ke dalam konteks ... Semoga ini bisa bermanfaat;)
Radim Köhler
7
@Radim Köhler - Saya telah mencoba contoh ini. itu bekerja tanpa AOT. Tetapi ketika saya mencoba menjalankan ini dengan AOT ia menunjukkan kesalahan "Tidak ditemukan metadata NgModule untuk RuntimeComponentModule". bisa tolong bantu saya untuk menyelesaikan kesalahan ini.
Trusha
4
Jawabannya sendiri sempurna! Tetapi untuk aplikasi kehidupan nyata tidak praktis. Tim sudut harus memberikan solusi untuk ini dalam kerangka kerja, karena ini adalah persyaratan umum dalam aplikasi bisnis. Jika tidak, harus ditanyakan apakah Angular 2 adalah platform yang tepat untuk aplikasi bisnis.
Karl
58

EDIT (26/08/2017) : Solusi di bawah ini berfungsi baik dengan Angular2 dan 4. Saya telah memperbaruinya untuk berisi variabel templat dan klik penangan dan mengujinya dengan Angular 4.3.
Untuk Angular4, ngComponentOutlet seperti yang dijelaskan dalam jawaban Ophir adalah solusi yang jauh lebih baik. Tetapi sekarang ini belum mendukung input & output . Jika [PR ini] ( https://github.com/angular/angular/pull/15362] diterima, akan dimungkinkan melalui instance komponen yang dikembalikan oleh event create.
Ng-dynamic-component mungkin yang terbaik dan paling sederhana solusi sama sekali, tapi saya belum mengujinya.

@Long Field's jawaban tepat! Berikut contoh (sinkron) lain:

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

Langsung di http://plnkr.co/edit/fdP9Oc .

Rene Hamburger
sumber
3
Saya akan mengatakan, bahwa ini adalah contoh bagaimana menulis kode sesedikit mungkin untuk melakukan hal yang sama seperti pada jawaban saya stackoverflow.com/a/38888009/1679310 . Dalam hal ini harus berguna (sebagian besar templat menghasilkan-ulang) ketika kondisi berubah ... ngAfterViewInitpanggilan sederhana dengan const templatetidak akan berfungsi. Tetapi jika tugas Anda adalah mengurangi pendekatan yang dijelaskan di atas (buat template, buat komponen, buat modul, kompilasi, buat pabrik .. buat contoh) ... Anda mungkin melakukannya
Radim Köhler
Terima kasih atas solusinya: Saya mengalami masalah saat memuat templateUrl dan gaya, saya mendapatkan kesalahan berikut: Tidak ada implementasi ResourceLoader yang disediakan. Tidak dapat membaca url localhost: 3000 / app / pages / pages_common.css , ada ide apa yang saya lewatkan?
Gerardlamo
Mungkinkah untuk mengompilasi template html dengan data spesifik untuk sel di grid seperti kontrol.? plnkr.co/edit/vJHUCnsJB7cwNJr2cCwp?p=preview Dalam plunker ini, bagaimana saya bisa mengkompilasi dan menampilkan gambar di kolom terakhir.? Ada bantuan.?
Karthick
1
@monnef, kamu benar. Saya tidak memeriksa log konsol. Saya telah menyesuaikan kode untuk menambahkan komponen di ngOnInit daripada kait ngAfterViewInit, karena yang pertama dipicu sebelum dan yang terakhir setelah deteksi perubahan. (Lihat github.com/angular/angular/issues/10131 dan utas sejenisnya.)
Rene Hamburger
1
rapi dan sederhana. Bekerja seperti yang diharapkan ketika melayani melalui browser di dev. Tetapi apakah ini bekerja dengan AOT? Ketika aplikasi dijalankan di PROD setelah kompilasi, saya mendapatkan "Kesalahan: Runtime compiler tidak dimuat" saat ini kompilasi komponen dicoba. (btw, saya menggunakan Ionic 3.5)
mymo
52

Saya pasti tiba di pesta terlambat, tidak ada solusi di sini yang tampaknya membantu saya - terlalu berantakan dan merasa seperti terlalu banyak solusi.

Apa yang saya akhirnya lakukan adalah menggunakan Angular 4.0.0-beta.6's ngComponentOutlet .

Ini memberi saya solusi terpendek, paling sederhana semua ditulis dalam file komponen dinamis.

  • Berikut ini adalah contoh sederhana yang baru saja menerima teks dan menempatkannya dalam templat, tetapi jelas Anda dapat mengubah sesuai dengan kebutuhan Anda:
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;
  }
}
  • Penjelasan singkat:
    1. my-component - komponen tempat komponen dinamis di-render
    2. DynamicComponent - komponen yang akan dibangun secara dinamis dan dirender di dalam komponen-saya

Jangan lupa untuk memutakhirkan semua perpustakaan sudut ke ^ Angular 4.0.0

Semoga ini bisa membantu, semoga berhasil!

MEMPERBARUI

Juga berfungsi untuk sudut 5.

Ophir Stern
sumber
3
Ini bekerja sangat baik untuk saya dengan Angular4. Satu-satunya penyesuaian yang harus saya lakukan adalah dapat menentukan modul impor untuk RuntimeComponentModule yang dibuat secara dinamis.
Rahul Patel
8
Berikut ini adalah contoh cepat mulai dari Angular Quickstart: embed.plnkr.co/9L72KpobVvY14uiQjo4p
Rahul Patel
5
Apakah solusi ini berfungsi dengan "ng build --prod"? Tampaknya kelas kompiler dan AoT tidak cocok bersama atm.
Pierre Chavaroche
2
@ OphirStern Saya juga menemukan bahwa pendekatan berfungsi dengan baik di Angular 5 tetapi TIDAK dengan flag build --prod.
TaeKwonJoe
2
Saya mengujinya dengan sudut 5 (5.2.8) menggunakan JitCompilerFactory dan menggunakan flag --prod tidak berfungsi! Adakah yang punya solusi? (BTW JitCompilerFactory tanpa --prod flag bekerja dengan sempurna)
Frank
20

2019 jawaban Juni

Kabar baik! Tampaknya paket @ angular / cdk sekarang memiliki dukungan kelas satu untuk portal !

Pada saat penulisan, saya tidak menemukan dokumen resmi di atas sangat membantu (terutama berkenaan dengan mengirim data ke dan menerima peristiwa dari komponen dinamis). Singkatnya, Anda harus:

Langkah 1) Perbarui AppModule

Impor PortalModuledari @angular/cdk/portalpaket dan daftarkan komponen dinamis Anda di dalamnyaentryComponents

@NgModule({
  declarations: [ ..., AppComponent, MyDynamicComponent, ... ]
  imports:      [ ..., PortalModule, ... ],
  entryComponents: [ ..., MyDynamicComponent, ... ]
})
export class AppModule { }

Langkah 2. Opsi A: Jika Anda TIDAK perlu memasukkan data ke dalam dan menerima acara dari komponen dinamis Anda :

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add child component</button>
    <ng-template [cdkPortalOutlet]="myPortal"></ng-template>
  `
})
export class AppComponent  {
  myPortal: ComponentPortal<any>;
  onClickAddChild() {
    this.myPortal = new ComponentPortal(MyDynamicComponent);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child.</p>`
})
export class MyDynamicComponent{
}

Lihat itu dalam aksi

Langkah 2. Opsi B: Jika Anda perlu memasukkan data ke dalam dan menerima acara dari komponen dinamis Anda :

// A bit of boilerplate here. Recommend putting this function in a utils 
// file in order to keep your component code a little cleaner.
function createDomPortalHost(elRef: ElementRef, injector: Injector) {
  return new DomPortalHost(
    elRef.nativeElement,
    injector.get(ComponentFactoryResolver),
    injector.get(ApplicationRef),
    injector
  );
}

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClickAddChild()">Click to add random child component</button>
    <div #portalHost></div>
  `
})
export class AppComponent {

  portalHost: DomPortalHost;
  @ViewChild('portalHost') elRef: ElementRef;

  constructor(readonly injector: Injector) {
  }

  ngOnInit() {
    this.portalHost = createDomPortalHost(this.elRef, this.injector);
  }

  onClickAddChild() {
    const myPortal = new ComponentPortal(MyDynamicComponent);
    const componentRef = this.portalHost.attach(myPortal);
    setTimeout(() => componentRef.instance.myInput 
      = '> This is data passed from AppComponent <', 1000);
    // ... if we had an output called 'myOutput' in a child component, 
    // this is how we would receive events...
    // this.componentRef.instance.myOutput.subscribe(() => ...);
  }
}

@Component({
  selector: 'app-child',
  template: `<p>I am a child. <strong>{{myInput}}</strong></p>`
})
export class MyDynamicComponent {
  @Input() myInput = '';
}

Lihat itu dalam aksi

Stephen Paul
sumber
1
Bung, Anda baru saja dipaku. Yang ini akan mendapat perhatian. Saya tidak percaya betapa sulitnya menambahkan komponen dinamis sederhana di Angular sampai saya perlu melakukannya. Ini seperti melakukan reset dan kembali ke masa pra-JQuery.
Gi1ber7
2
@ Gi1ber7 saya tahu kan? Mengapa mereka butuh waktu selama ini?
Stephen Paul
1
Pendekatan yang bagus, tetapi apakah Anda tahu cara meneruskan parameter ke ChildComponent?
Snook
1
@ Lihat ini dapat menjawab pertanyaan Anda stackoverflow.com/questions/47469844/...
Stephen Paul
4
@StephenPaul Bagaimana Portalperbedaan pendekatan ini ngTemplateOutletdan ngComponentOutlet? 🤔
Glenn Mohammad
18

Saya memutuskan untuk memadatkan semua yang saya pelajari menjadi satu file . Ada banyak hal yang perlu diperhatikan di sini terutama dibandingkan sebelum RC5. Perhatikan bahwa file sumber ini termasuk AppModule dan 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 {}`
Stephen Paul
sumber
10

Saya punya contoh sederhana untuk menunjukkan bagaimana melakukan komponen dinamis 2 rc6 sudut.

Katakanlah, Anda memiliki template html dinamis = template1 dan ingin memuat dinamis, pertama-tama bungkus ke dalam komponen

@Component({template: template1})
class DynamicComponent {}

di sini template1 sebagai html, mungkin mengandung komponen ng2

Dari rc6, @NgModule harus membungkus komponen ini. @NgModule, sama seperti module di anglarJS 1, ia memisahkan bagian yang berbeda dari aplikasi ng2, jadi:

@Component({
  template: template1,

})
class DynamicComponent {

}
@NgModule({
  imports: [BrowserModule,RouterModule],
  declarations: [DynamicComponent]
})
class DynamicModule { }

(Di sini impor RouterModule seperti dalam contoh saya ada beberapa komponen rute di html saya seperti yang Anda lihat nanti)

Sekarang Anda dapat mengkompilasi DynamicModule sebagai: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

Dan kita perlu memasukkan di atas di app.moudule.ts untuk memuatnya, silakan lihat app.moudle.ts saya. Untuk detail lebih lanjut dan lengkap, periksa: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts dan app.moudle.ts

dan lihat demo: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview

Lapangan Panjang
sumber
3
Jadi, Anda telah mendeklarasikan module1, module2, module3. Dan jika Anda memerlukan konten templat "dinamis" yang lain, Anda harus membuat definisi (file) dari moudle4 (module4.ts), kan? Jika ya, itu sepertinya tidak dinamis. Itu statis, bukan? Atau apakah saya melewatkan sesuatu?
Radim Köhler
Dalam "template1" di atas adalah string html, Anda dapat memasukkan apa pun di dalamnya dan kami menyebutnya template dinamis, karena pertanyaan ini diajukan
Long Field
6

Dalam sudut 7.x saya menggunakan elemen sudut untuk ini.

  1. Instal @ angular-elements npm i @ angular / elements -s

  2. Buat layanan aksesori.

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

Perhatikan bahwa tag elemen khusus Anda harus berbeda dengan pemilih komponen sudut. di AppUserIconComponent:

...
selector: app-user-icon
...

dan dalam hal ini nama tag khusus saya menggunakan "ikon pengguna".

  1. Maka Anda harus memanggil register di AppComponent:
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
  1. Dan sekarang di sembarang tempat kode Anda, Anda dapat menggunakannya seperti ini:
dynamicComponents.create('user-icon', {user:{...}});

atau seperti ini:

const html = `<div class="wrapper"><user-icon class="user-icon" user='${JSON.stringify(rec.user)}'></user-icon></div>`;

this.content = this.domSanitizer.bypassSecurityTrustHtml(html);

(dalam templat):

<div class="comment-item d-flex" [innerHTML]="content"></div>

Perhatikan bahwa dalam kasus kedua Anda harus meneruskan objek dengan JSON.stringify dan setelah itu parse lagi. Saya tidak dapat menemukan solusi yang lebih baik.

Oleg Pnk
sumber
Pendekatan interresting, tetapi Anda harus menargetkan es2015 (jadi tidak ada dukungan untuk IE11) di tsconfig.json Anda, namun itu akan gagal padadocument.createElement(tagName);
Snook
Hai, seperti yang Anda sebutkan cara untuk menangani input, jadi bisakah output komponen anak dapat ditangani seperti ini juga?
Mustahsan
5

Memecahkan ini dalam versi Angular 2 Final hanya dengan menggunakan direktif dynamicComponent dari ng-dynamic .

Pemakaian:

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

Di mana templat adalah templat dinamis dan konteks Anda dapat disetel ke model data dinamis apa pun yang Anda inginkan untuk diikat templat.

Richard Houltz
sumber
Pada saat penulisan Angular 5 dengan AOT tidak mendukung ini karena JIT compiler tidak termasuk dalam bundel. Tanpa AOT itu bekerja seperti pesona :)
Richard Houltz
apakah ini masih berlaku untuk angular 7+?
Carlos E
4

Saya ingin menambahkan beberapa detail di atas posting yang sangat bagus ini oleh Radim.

Saya mengambil solusi ini dan mengerjakannya sebentar dan dengan cepat berlari ke beberapa keterbatasan. Saya hanya akan menguraikan itu dan kemudian memberikan solusi untuk itu juga.

  • Pertama-tama saya tidak dapat membuat detail dinamis di dalam detail dinamis (pada dasarnya UI dinamis satu sama lain).
  • Masalah berikutnya adalah bahwa saya ingin membuat detail dinamis di dalam salah satu bagian yang tersedia dalam solusi. Itu tidak mungkin dengan solusi awal juga.
  • Terakhir tidak mungkin menggunakan URL templat pada bagian dinamis seperti editor string.

Saya membuat pertanyaan lain berdasarkan posting ini, tentang bagaimana mencapai batasan-batasan ini, yang dapat ditemukan di sini:

kompilasi template dinamis rekursif dalam angular2

Saya hanya akan menguraikan jawaban untuk keterbatasan ini, jika Anda mengalami masalah yang sama seperti saya, karena itu membuat solusinya menjadi lebih fleksibel. Akan luar biasa jika plunker awal diperbarui dengan itu juga.

Untuk mengaktifkan detail dinamis bersarang di dalam satu sama lain, Anda harus menambahkan DynamicModule.forRoot () dalam pernyataan impor di type.builder.ts

protected createComponentModule (componentType: any) {
    @NgModule({
    imports: [
        PartsModule, 
        DynamicModule.forRoot() //this line here
    ],
    declarations: [
        componentType
    ],
    })
    class RuntimeComponentModule
    {
    }
    // a module for just this Type
    return RuntimeComponentModule;
}

Selain itu tidak mungkin digunakan <dynamic-detail> di dalam salah satu bagian yang menjadi editor string atau editor teks.

Untuk mengaktifkannya Anda harus mengubah parts.module.tsdandynamic.module.ts

Di dalam parts.module.tsAnda harus menambahkan DynamicDetaildiDYNAMIC_DIRECTIVES

export const DYNAMIC_DIRECTIVES = [
   forwardRef(() => StringEditor),
   forwardRef(() => TextEditor),
   DynamicDetail
];

Juga di dynamic.module.tsAnda harus menghapus dynamicDetail karena sekarang menjadi bagian dari bagian

@NgModule({
   imports:      [ PartsModule ],
   exports:      [ PartsModule],
})

Plunker yang dimodifikasi dapat ditemukan di sini: http://plnkr.co/edit/UYnQHF?p=preview (Saya tidak menyelesaikan masalah ini, saya hanya pembawa pesan :-D)

Akhirnya tidak mungkin untuk menggunakan templateurl di bagian yang dibuat pada komponen dinamis. Solusi (atau solusi. Saya tidak yakin apakah itu bug sudut atau penggunaan kerangka kerja yang salah) adalah membuat kompiler di konstruktor alih-alih menyuntikkannya.

    private _compiler;

    constructor(protected compiler: RuntimeCompiler) {
        const compilerFactory : CompilerFactory =
        platformBrowserDynamic().injector.get(CompilerFactory);
        this._compiler = compilerFactory.createCompiler([]);
    }

Kemudian gunakan _compileruntuk mengkompilasi, lalu templateUrls juga diaktifkan.

return new Promise((resolve) => {
        this._compiler
            .compileModuleAndAllComponentsAsync(module)
            .then((moduleWithFactories) =>
            {
                let _ = window["_"];
                factory = _.find(moduleWithFactories.componentFactories, { componentType: type });

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });

Semoga ini bisa membantu orang lain!

Salam, Morten

Morten Skjoldager
sumber
4

Menindaklanjuti jawaban Radmin yang luar biasa, ada sedikit penyesuaian yang diperlukan untuk semua orang yang menggunakan angular-cli versi 1.0.0-beta.22 dan yang lebih tinggi.

COMPILER_PROVIDERStidak dapat lagi diimpor (untuk detail lihat angular-cli GitHub ).

Jadi solusinya ada untuk tidak menggunakan COMPILER_PROVIDERSdan JitCompilerdi providersbagian sama sekali, tetapi gunakan JitCompilerFactorydari '@ angular / compiler' sebagai gantinya seperti ini di dalam kelas tipe builder:

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

Seperti yang Anda lihat, itu tidak dapat disuntikkan dan dengan demikian tidak memiliki ketergantungan dengan DI. Solusi ini juga dapat digunakan untuk proyek yang tidak menggunakan angular-cli.

Sebastian
sumber
1
Terima kasih atas saran ini, namun, saya mengalami "Tidak ada metadata NgModule yang ditemukan untuk 'DynamicHtmlModule'". Implementasi saya didasarkan pada stackoverflow.com/questions/40060498/…
Cybey
2
Adakah yang sudah menggunakan JitCompiletFactory dengan sampel AOT? Saya memiliki kesalahan yang sama dengan @Cybey
user2771738
2

Saya sendiri mencoba melihat bagaimana cara memperbarui RC4 ke RC5 dan karenanya saya menemukan entri ini dan pendekatan baru untuk pembuatan komponen dinamis masih menyimpan sedikit misteri bagi saya, jadi saya tidak akan menyarankan apa pun pada penyelesai pabrik komponen.

Tapi, yang bisa saya sarankan adalah pendekatan yang lebih jelas untuk pembuatan komponen pada skenario ini - cukup gunakan sakelar di templat yang akan membuat penyunting string atau penyunting teks sesuai dengan beberapa kondisi, seperti ini:

<form [ngSwitch]="useTextarea">
    <string-editor *ngSwitchCase="false" propertyName="'code'" 
                 [entity]="entity"></string-editor>
    <text-editor *ngSwitchCase="true" propertyName="'code'" 
                 [entity]="entity"></text-editor>
</form>

Dan omong-omong, "[" dalam ekspresi [prop] memiliki makna, ini menunjukkan data mengikat satu arah, maka Anda dapat dan bahkan harus menghilangkannya jika Anda tahu bahwa Anda tidak perlu mengikat properti ke variabel.

zii
sumber
1
Itu akan menjadi cara untuk pergi .. jika switch/ caseberisi beberapa keputusan. Tetapi bayangkan bahwa templat yang dihasilkan bisa sangat besar ... dan berbeda untuk setiap entitas, berbeda oleh keamanan, berbeda dengan status entitas, menurut setiap jenis properti (nomor, tanggal, referensi ... editor) ... Dalam kasus seperti itu, Memecahkan ini dengan templat html ngSwitchakan membuat file besar, sangat sangat besar html.
Radim Köhler
Oh, aku setuju denganmu. Saya mempunyai skenario seperti ini di sini, saat ini karena saya mencoba memuat komponen aplikasi utama tanpa mengetahui sebelum mengkompilasi kelas tertentu yang akan ditampilkan. Meskipun kasus khusus ini tidak memerlukan pembuatan komponen dinamis.
zii
1

Ini adalah contoh kontrol Formulir dinamis yang dihasilkan dari server.

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

Contoh ini adalah kontrol Form dinamis dalam komponen add (Di sinilah Anda bisa mendapatkan Formcontrols dari server). Jika Anda melihat metode addcomponent Anda dapat melihat Kontrol Formulir. Dalam contoh ini saya tidak menggunakan bahan bersudut, tetapi berfungsi (saya menggunakan @ kerja). Ini target ke angular 6, tetapi berfungsi di semua versi sebelumnya.

Perlu menambahkan JITComplierFactory untuk AngularVersion 5 ke atas.

Terima kasih

Vijay

Vijay Anand Kannan
sumber
0

Untuk kasus khusus ini sepertinya menggunakan arahan untuk secara dinamis membuat komponen akan menjadi pilihan yang lebih baik. Contoh:

Di HTML tempat Anda ingin membuat komponen

<ng-container dynamicComponentDirective [someConfig]="someConfig"></ng-container>

Saya akan mendekati dan merancang arahan dengan cara berikut.

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

Jadi dalam teks komponen Anda, string, tanggal, apa pun - apa pun konfigurasi yang Anda berikan dalam HTML pada ng-containerelemen akan tersedia.

Konfigurasi,, yourConfigbisa sama dan mendefinisikan metadata Anda.

Bergantung pada konfigurasi atau tipe input Anda, arahan harus bertindak sesuai dan dari jenis yang didukung, itu akan membuat komponen yang sesuai. Jika tidak, ini akan mencatat kesalahan.

katautt
sumber
-1

Membangun di atas jawaban oleh Ophir Stern, di sini adalah varian yang bekerja dengan AoT di Angular 4. Satu-satunya masalah yang saya miliki adalah saya tidak bisa menyuntikkan layanan apa pun ke dalam DynamicComponent, tetapi saya bisa hidup dengan itu.

Catatan: Saya belum diuji dengan Angular 5.

import { Component, OnInit, Input, NgModule, NgModuleFactory, Compiler, EventEmitter, Output } from '@angular/core';
import { JitCompilerFactory } from '@angular/compiler';

export function createJitCompiler() {
  return new JitCompilerFactory([{
    useDebug: false,
    useJit: true
  }]).createCompiler();
}

type Bindings = {
  [key: string]: any;
};

@Component({
  selector: 'app-compile',
  template: `
    <div *ngIf="dynamicComponent && dynamicModule">
      <ng-container *ngComponentOutlet="dynamicComponent; ngModuleFactory: dynamicModule;">
      </ng-container>
    </div>
  `,
  styleUrls: ['./compile.component.scss'],
  providers: [{provide: Compiler, useFactory: createJitCompiler}]
})
export class CompileComponent implements OnInit {

  public dynamicComponent: any;
  public dynamicModule: NgModuleFactory<any>;

  @Input()
  public bindings: Bindings = {};
  @Input()
  public template: string = '';

  constructor(private compiler: Compiler) { }

  public ngOnInit() {

    try {
      this.loadDynamicContent();
    } catch (err) {
      console.log('Error during template parsing: ', err);
    }

  }

  private loadDynamicContent(): void {

    this.dynamicComponent = this.createNewComponent(this.template, this.bindings);
    this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));

  }

  private createComponentModule(componentType: any): any {

    const runtimeComponentModule = NgModule({
      imports: [],
      declarations: [
        componentType
      ],
      entryComponents: [componentType]
    })(class RuntimeComponentModule { });

    return runtimeComponentModule;

  }

  private createNewComponent(template: string, bindings: Bindings): any {

    const dynamicComponent = Component({
      selector: 'app-dynamic-component',
      template: template
    })(class DynamicComponent implements OnInit {

      public bindings: Bindings;

      constructor() { }

      public ngOnInit() {
        this.bindings = bindings;
      }

    });

    return dynamicComponent;

  }

}

Semoga ini membantu.

Bersulang!

Pemarah
sumber