Angular 2.0 ile dinamik Bileşeni derlemek için dinamik şablonu nasıl kullanabilir / oluşturabilirim?


197

Dinamik olarak bir şablon oluşturmak istiyorum. Bu ComponentType, çalışma zamanında bir inşa etmek ve barındırma Bileşeni içinde bir yere yerleştirmek (hatta değiştirmek) için kullanılmalıdır.

RC4 kullanana kadar ComponentResolverRC5 ile şu mesajı alıyorum:

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.

Bu belgeyi buldum ( Açısal 2 Senkron Dinamik Bileşen Oluşturma )

Ve anlayabiliyorum da

  • Dinamiğinin Kind ngIfile ComponentFactoryResolver. Bilinen bileşenleri@Component({entryComponents: [comp1, comp2], ...}) - kullanabilirim.resolveComponentFactory(componentToRender);
  • Gerçek çalışma zamanı derleme, Compiler...

Ama soru bunun nasıl kullanılacağıdır Compiler? Yukarıdaki not şunu aramam gerektiğini söylüyor:Compiler.compileComponentSync/Async - nasıl?

Örneğin. Bir tür ayar için (bazı yapılandırma koşullarına bağlı olarak) bu tür bir şablon oluşturmak istiyorum

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

ve başka bir durumda bu ( string-editorile değiştirilir text-editor)

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

Ve böylece ( editorsözellik türlerine göre farklı sayı / tarih / referans , bazı kullanıcılar için bazı özellikleri atladı ...) . yani bu bir örnektir, gerçek yapılandırma çok daha farklı ve karmaşık şablonlar oluşturabilir.

Şablon değişiyor, bu yüzden ComponentFactoryResolvermevcut olanları kullanamıyorum ve geçemiyorum ... ile bir çözüme ihtiyacım var Compiler.


Bulduğum çözüm o kadar güzeldi ki, bu soruyu bulan herkesin şu anda en altta yer alan cevabımıza bakmasını istiyorum. :)
Richard Houltz


İşte her cevap ile ilgili sorun ve $compileaslında bu yöntemlerin yapamadığı ne yapabilirim - ben sadece bir üçüncü tarafın sayfa ve ajax çağrıları aracılığıyla gelir gibi HTML derlemek istiyorum bir uygulama oluşturuyorum. HTML'yi sayfadan kaldıramıyorum ve kendi şablonuma yerleştiremiyorum. İç çekiş
Augie Gardner

@AugieGardner Bunun tasarımla mümkün olmamasının bir nedeni var. Açısal, kötü mimari kararlar veya bazı insanların sahip olduğu eski sistemler için hatalı değildir. Mevcut HTML kodunu ayrıştırmak istiyorsanız, Açısal WebComponents ile mükemmel şekilde çalıştığı için başka bir çerçeve kullanabilirsiniz. Deneyimsiz programcıların ordularını yönlendirmek için net sınırlar koymak, birkaç eski sistem için kirli saldırılara izin vermekten daha önemlidir.
Phil

Yanıtlar:


163

EDIT - 2.3.0 (2016-12-07) ile ilgili

NOT: önceki sürüm için çözüm bulmak için bu gönderinin geçmişini kontrol edin

Benzer konu Angular 2'de $ compile eşdeğeri burada tartışılmaktadır . Biz kullanmak gerekir JitCompilerve NgModule. NgModuleAngular2 ile ilgili daha fazla bilgiyi buradan edinebilirsiniz:

Kısaca

Orada çalışan bir plunker / example (dinamik şablon, dinamik bileşen tipi, dinamik modül, JitCompiler... eylemde)

Temel öğe:
1) Şablon
2 oluşturma )ComponentFactory önbellekte bulma - 7'ye git )
3) - oluşturma Component
4) - oluşturma Module
5) - derleme Module
6) - dönüş (ve daha sonra kullanmak üzere önbellek) ComponentFactory
7) Hedef'i kullanma ve ComponentFactorybir Örnek oluşturma dinamikComponent

İşte bir kod snippet'i (daha fazlası burada ) - Özel Oluşturucumuz yeni oluşturulmuş / önbelleğe alınmış olarak dönüyor ComponentFactoryve Hedef yer tutucusunun görünümü,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;
        //...
    });

İşte bu - kısaca. Daha fazla bilgi almak için .. aşağıyı okuyun

.

TP & DR

Bir pasajı gözlemleyin ve bazı pasajların daha fazla açıklama gerektirmesi durumunda ayrıntıları okumak için geri dönün

.

Ayrıntılı açıklama - Angular2 RC6 ++ ve çalışma zamanı bileşenleri

Bu senaryonun açıklamasının altında ,

  1. bir modül oluşturun PartsModule:NgModule (küçük parçaların tutucusu)
  2. DynamicModule:NgModuleDinamik bileşenimizi içerecek başka bir modül oluşturun (ve PartsModuledinamik olarak referans verin )
  3. dinamik Şablon oluşturma (basit yaklaşım)
  4. yeni Componenttür oluştur (yalnızca şablon değiştiyse)
  5. yeni oluştur RuntimeModule:NgModule. Bu modül önceden oluşturulan Componenttürü içerecektir
  6. JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)almak için çağırComponentFactory
  7. DynamicComponentHedefi Görüntüle yer tutucusunun işinin bir örneğini oluşturmak veComponentFactory
  8. atamak @Inputsiçin yeni bir örneği (geçişten INPUTiçin TEXTAREAdüzenleme) , tüketmek@Outputs

NgModule

Bir NgModules lazım .

Çok basit bir örnek göstermek isterken , bu durumda, üç modüle ihtiyacım olacaktı (aslında 4 - ama AppModule'u saymıyorum) . Lütfen, gerçekten sağlam bir dinamik bileşen üreticisi için basit bir snippet yerine bunu alın .

Tüm küçük bileşenler için bir modül olacaktır , örneğin string-editor, text-editor ( date-editor, number-editor...)

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

Nerede DYNAMIC_DIRECTIVESgenişletilebilir ve dinamik Bileşen şablon / türü için kullanılan tüm küçük parçaları tutmak için tasarlanmıştır. Uygulamayı / parçaları / parçaları kontrol edin.

İkincisi, Dinamik malzeme işleme için modül olacak. Bu hosting bileşenleri ve singleton olacak bazı sağlayıcıları içerecektir. Bu nedenle onları standart bir şekilde yayınlayacağız -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
            ], 
        };
    }
}

İçindeki kullanımını kontrol edin forRoot().AppModule

Son olarak, bir adhoc, runtime modülüne ihtiyacımız olacak ... ama bu daha sonra DynamicTypeBuilderişin bir parçası olarak yaratılacak .

Dördüncü modül, uygulama modülü derleyici sağlayıcılarını beyan eden modüldür:

...
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 hakkında daha fazlasını okuyun (okuyun) :

Bir şablon oluşturucu

Örneğimizde bu tür varlıkların detaylarını işleyeceğiz

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

Bir oluşturmak için template, bu dalgıçta bu basit / saf oluşturucuyu kullanıyoruz.

Gerçek çözüm, gerçek bir şablon oluşturucu, uygulamanızın çok şey yapabileceği yerdir

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

Burada bir hile - bilinen bazı özellikleri kullanan bir şablon oluşturur, örn entity. Bu tür özellikler (-ies), bir sonraki oluşturacağımız dinamik bileşenin bir parçası olmalıdır.

Bunu biraz daha kolaylaştırmak için Şablon oluşturucumuzun kullanabileceği özellikleri tanımlamak için bir arayüz kullanabiliriz. Bu, dinamik Bileşen tipimiz tarafından uygulanacaktır.

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

Bir ComponentFactoryinşaatçı

Burada çok önemli olan şey akılda tutmaktır:

bileşen türümüz, bizim ürünümüzle birlikte oluşturulabilir DynamicTypeBuilder, ancak yalnızca şablonu (yukarıda oluşturulmuştur) ile farklılık gösterebilir . Bileşenlerin özellikleri (girişler, çıkışlar veya bazıları korumalı) hala aynıdır. Farklı özelliklere ihtiyacımız varsa, Şablon ve Tür Oluşturucu'nun farklı kombinasyonunu tanımlamalıyız

Yani, çözümümüzün özüne dokunuyoruz. Oluşturucu, 1) oluşturur ComponentType2) NgModule3) derler ComponentFactory4) daha sonra yeniden kullanmak için önbellek .

Almak zorunda olduğumuz bir bağımlılık:

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

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

Ve işte bir pasaj nasıl elde edilir 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);
            });
    });
}

Yukarıda hem ve hem de önbellek oluşturuyoruz . Çünkü şablon (aslında hepsinin gerçek dinamik kısmı) aynı ise .. tekrar kullanabilirizComponentModule

Ve burada, çalışma zamanında süslü sınıflar / türler oluşturmanın gerçekten harika yolunu temsil eden iki yöntem var . Sadece @Componentdeğil, aynı zamanda@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;
}

Önemli:

bileşen dinamik türlerimiz yalnızca şablondan farklılık gösterir. Bu gerçeği önbelleklemek için kullanıyoruz. Bu gerçekten çok önemli. Angular2 de bunları önbelleğe alır .. türüne göre . Ve aynı şablon dizeleri için yeni türler yaratırsak ... bellek sızıntıları üretmeye başlayacağız.

ComponentFactory barındırma bileşeni tarafından kullanılır

Son parça, dinamik bileşenimiz için hedefi barındıran bir bileşendir, örn <div #dynamicContentPlaceHolder></div>. Bir referans alıyoruz ve ComponentFactorybir bileşen oluşturmak için kullanıyoruz . Kısacası ve işte bu bileşenin tüm parçaları (gerekirse, burada açık dalgıç) )

Öncelikle ithalat beyanlarını özetleyelim:

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

Sadece şablon ve bileşen oluşturucuları alıyoruz. Sonraki örnek için gerekli özellikler (yorumlarda daha fazla)

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

Bu basit senaryoda, barındırma bileşenimiz yoktur @Input. Dolayısıyla değişikliklere tepki vermek zorunda değildir. Ancak bu gerçeğe rağmen (ve gelecekteki değişikliklere hazır olmak için) - bileşen zaten varsa (ilk olarak) bazı bayraklar eklemeliyiz başlatılmışsa eklememiz gerekir. Ve ancak o zaman büyüye başlayabiliriz.

Sonunda bileşen oluşturucumuzu kullanacağız ve sadece derlendi / önbelleğe alındı ComponentFacotry . Bizim Hedef tutucu örneğini istenecek o fabrika.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;
        //...
    });
}

küçük uzatma

Ayrıca, düzgün bir şekilde yapabilmek için derlediğimiz şablona bir referans göndermemiz destroy()gerekir.

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

tamam

Hemen hemen bu kadar. Dinamik olarak inşa edilmiş herhangi bir şeyi yok etmeyi unutmayın (ngOnDestroy) . Ayrıca, dinamik önbelleğe aldığınızdan emin olun typesve modulestek fark şablonuysa.

Hepsini burada kontrol edin

bu yayının önceki sürümlerini (ör. RC5 ile ilgili) görmek için geçmişi kontrol edin


50
bu kadar karmaşık bir çözüm gibi görünüyor, kullanımdan kaldırılmış olan çok basit ve açıktı, bunu yapmanın başka yolu var mı?
tibbus

3
Ben @tibbus ile aynı şekilde düşünüyorum: bu eskiden kodu ile olduğundan daha karmaşık var. Yine de cevabınız için teşekkürler.
Lucio Mollinedo

5
@ ribsies notunuz için teşekkürler. Bir şeyi netleştireyim. Diğer birçok cevap basitleştirmeye çalışıyor . Ama bunu açıklamaya ve gerçek kullanıma kapalı bir senaryoda göstermeye çalışıyorum . Bir şeyleri önbelleğe almamız gerekir, yeniden yaratma vb type.builder.ts. bağlam ... Umarım faydalı olabilir;)
Radim Köhler

7
@Radim Köhler - Bu örneği denedim. AOT olmadan çalışıyor. Ama bunu AOT ile çalıştırmayı denediğimde "RuntimeComponentModule için hiçbir NgModule meta verisi bulunamadı" hatasını gösteriyor. plz bu hatayı çözmek için bana yardımcı olabilir misiniz.
Trusha

4
Cevabın kendisi mükemmel! Ancak gerçek hayattaki uygulamalar için uygulanamaz. Açısal ekip, iş uygulamalarında ortak bir gereklilik olduğu için bu çerçevede bir çözüm sağlamalıdır. Değilse, Angular 2'nin iş uygulamaları için doğru platform olup olmadığı sorulmalıdır.
Karl

58

EDIT (26/08/2017) : Aşağıdaki çözüm Angular2 ve 4 ile iyi çalışıyor. Bir şablon değişkeni ve tıklama işleyicisi içerecek şekilde güncelledim ve Açısal 4.3 ile test ettim.
Angular4, anlatıldığı gibi ngComponentOutlet için Ophir cevabı çok daha iyi bir çözümdür. Ancak şu anda henüz girdi ve çıktıları desteklemiyor . [Bu PR] ( https://github.com/angular/angular/pull/15362] kabul edilirse , create olayı tarafından döndürülen bileşen örneği aracılığıyla mümkün olabilir.
Ng-dynamic-component en iyi ve en basit olabilir çözüm, ancak henüz test etmedim.

@ Long Field'ın yanıtı açık! İşte başka (senkron) bir örnek:

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 adresinde canlı .


3
Benim cevabım stackoverflow.com/a/38888009/1679310 ile aynı şeyi yapmak için mümkün olduğunca az kod yazmanın bir örneği olduğunu söyleyebilirim . Durum değiştiğinde yararlı bir durum olması gerekir (çoğunlukla RE üreten şablon) ... bir ile basit çağrı çalışmaz. Ancak göreviniz yukarıda açıklanan ayrıntılı yaklaşımı azaltmaksa (şablon oluştur, bileşen oluştur, modül oluştur, derle, fabrika oluştur .. örnek oluştur) ... büyük olasılıkla ngAfterViewInitconst template
başardınız

Çözüm için teşekkürler: TemplateUrl ve stilleri yüklerken sorun yaşıyorum, aşağıdaki hatayı alıyorum: Hiçbir ResourceLoader uygulaması sağlanmadı. Url localhost: 3000 / app / pages / pages_common.css dosyasını okuyamıyorum , ne eksik olduğum hakkında bir fikriniz var mı?
Gerardlamo

Html şablonunu kontrol gibi ızgaradaki hücreye özgü verilerle derlemek mümkün olabilir mi? plnkr.co/edit/vJHUCnsJB7cwNJr2cCwp?p=preview Bu dalma makinesinde, görüntüyü nasıl derleyebilir ve son sütunda nasıl gösterebilirim? Herhangi bir yardım.?
Karthick

1
@monnef, haklısın. Konsol günlüğünü kontrol etmedim. Eski tetiklenir gibi ben ngOnInit yerine ngAfterViewInit kanca içinde bileşen eklemek için kod ayarladınız önce ve ikincisi sonrası değişim tespiti. (Bkz. Github.com/angular/angular/issues/10131 ve benzeri konular.)
Rene Hamburger

1
temiz ve basit. Geliştirici tarayıcı üzerinden sunarken beklendiği gibi çalıştı. Ama bu AOT ile çalışıyor mu? Uygulama derleme sonra PROD içinde çalıştırıldığında, bileşen derleme denendiği anda bir "Hata: Çalışma zamanı derleyicisi yüklü değil" alıyorum. (btw, Ionic 3.5 kullanıyorum)
mymo

52

Partiye geç gelmiş olmalı, burada çözümlerin hiçbiri bana - çok dağınık görünüyordu ve çok fazla bir geçici çözüm gibi hissettim.

Ne kadar sona erdi Angular 4.0.0-beta.6's ngComponentOutlet kullanmaktır .

Bu bana dinamik bileşenin dosyasında yazılmış en kısa, en basit çözümü verdi.

  • İşte sadece metin alan ve bir şablona yerleştiren basit bir örnek, ancak açıkça ihtiyacınıza göre değiştirebilirsiniz:
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;
  }
}
  • Kısa açıklama:
    1. my-component - dinamik bir bileşenin oluşturulduğu bileşen
    2. DynamicComponent - dinamik olarak oluşturulacak bileşen ve bileşenimin içinde oluşturuluyor

Tüm açısal kütüphaneleri ^ Açısal 4.0.0'a yükseltmeyi unutmayın

Umarım bu yardımcı olur, iyi şanslar!

GÜNCELLEME

Açısal 5 için de çalışır.


3
Angular4 ile bu benim için harika çalıştı. Yapmam gereken tek ayar, dinamik olarak oluşturulan RuntimeComponentModule için içe aktarma modüllerini belirleyebilmemdi.
Rahul Patel

8
İşte Angular Quickstart'dan başlayarak kısa bir örnek: embed.plnkr.co/9L72KpobVvY14uiQjo4p
Rahul Patel

5
Bu çözüm "ng build --prod" ile çalışır mı? Derleyici sınıfı ve AoT, atm'ye uymuyor gibi görünüyor.
Pierre Chavaroche

2
@OphirStern Ayrıca yaklaşımın Açısal 5'te iyi çalıştığını, ancak --prod yapı bayrağıyla DEĞİL olduğunu keşfettim.
TaeKwonJoe

2
Ben JitCompilerFactory kullanarak açısal 5 (5.2.8) ile test ve --prod bayrağı kullanarak çalışmıyor! Herkesin bir çözümü var mı? (--Prod bayrağı olmadan BTW JitCompilerFactory kusursuz çalışır)
Frank

20

2019 Haziran Cevabı

Harika haber! @ Angular / cdk paketinin artık portallar için birinci sınıf desteği var gibi görünüyor !

Yazma tarihi itibariyle, yukarıdaki resmi dokümanları özellikle yararlı bulmadım (özellikle dinamik bileşenlere veri gönderme ve bunlardan etkinlik alma konusunda). Özet olarak şunları yapmanız gerekir:

Adım 1) AppModule

İthalat PortalModulegelen @angular/cdk/portalpaket ve dinamik bileşen kayıt (lar) içindeentryComponents

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

2. Adım. Seçenek A: Dinamik bileşenlerinize veri aktarmanız ve bu bileşenlerden etkinlik almanız GEREKMEZ :

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

Hareket halinde görün

2. Adım. Seçenek B: Dinamik bileşenlerinize veri aktarmanız ve bu bileşenlerden etkinlik almanız gerekiyorsa :

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

Hareket halinde görün


1
Dostum, sadece çivilendin. Bu dikkat çekecek. Yapmam gerekene kadar Angular'a basit bir dinamik bileşen eklemenin ne kadar zor olduğuna inanamıyorum. Sıfırlama yapmak ve JQuery öncesi zamanlara geri dönmek gibi.
Gi1ber7

2
@ Gi1ber7 Biliyorum doğru mu? Neden bu kadar uzun sürdü?
Stephen Paul

1
İyi bir yaklaşım, ancak parametreleri ChildComponent'e nasıl aktaracağınızı biliyor musunuz?
Snook

1
@Snook bu soruya cevap verebilir stackoverflow.com/questions/47469844/…
Stephen Paul

4
@StephenPaul Bu Portalyaklaşım ile ngTemplateOutletve arasındaki fark ngComponentOutletnedir? 🤔
Glenn Mohammad

18

Öğrendiğim her şeyi tek bir dosyaya sıkıştırmaya karar verdim . Burada özellikle RC5'ten önce alınacak çok şey var. Bu kaynak dosyanın AppModule ve AppComponent içerdiğini unutmayın.

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

10

Açısal 2 rc6 dinamik bileşen nasıl gösterileceğini göstermek için basit bir örnek var.

Diyelim ki dinamik bir html şablonunuz var = template1 ve dinamik yükleme yapmak istiyorsunuz, önce bileşene sarın

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

burada template1 html olarak, ng2 bileşeni içerebilir

Rc6, @NgModule bu bileşeni sarmak zorunda. @NgModule, tıpkı anglarJS 1'deki modül gibi, ng2 uygulamasının farklı bölümünü ayırır, bu yüzden:

@Component({
  template: template1,

})
class DynamicComponent {

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

(Burada örneğimde olduğu gibi RouterModule'u içe aktarın, daha sonra görebileceğiniz gibi html'imde bazı rota bileşenleri var)

Artık DynamicModule'ü şu şekilde derleyebilirsiniz: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

Ve yüklemek için app.moudule.ts içine koymamız gerekiyor, lütfen app.moudle.ts'e bakın. Daha fazla ve tüm ayrıntılar için: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts ve app.moudle.ts

ve demoya bakın: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview


3
Böylece, modül1, modül2, modül3 bildirdiniz. Ve başka bir "dinamik" şablon içeriğine ihtiyacınız varsa, moudle4 (module4.ts) şeklinde bir tanım (dosya) oluşturmanız gerekir, değil mi? Evet ise, bu dinamik görünmüyor. Statiktir, değil mi? Yoksa bir şey mi özlüyorum?
Radim Köhler

Yukarıda "template1" html dizesidir, içine bir şey koyabilirsiniz ve biz bu dinamik şablon diyoruz, bu soru soruyor gibi
Uzun Alan

6

Açısal 7.x'te bunun için açısal elemanlar kullandım.

  1. @ Açısal elemanlar npm i @ açısal / elemanlar -s

  2. Aksesuar hizmeti oluşturun.

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

Özel öğe etiketinizin açısal bileşen seçiciden farklı olması gerektiğini unutmayın. AppUserIconComponent içinde:

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

ve bu durumda özel etiket adı "kullanıcı simgesi" kullandım.

  1. O zaman AppComponent'te register'ı çağırmalısınız:
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
  1. Ve şimdi kodunuzun herhangi bir yerinde şu şekilde kullanabilirsiniz:
dynamicComponents.create('user-icon', {user:{...}});

ya da bunun gibi:

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

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

(şablonda):

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

İkinci durumda JSON.stringify ile nesneleri iletmeniz ve bundan sonra tekrar ayrıştırmanız gerektiğini unutmayın. Daha iyi bir çözüm bulamıyorum.


İnterresting yaklaşımı, ancak tsconfig.json'unuzda es2015'i (yani IE11 için destek yok) hedeflemeniz gerekecek, başarısız olacakdocument.createElement(tagName);
Snook

Merhaba, girişleri işlemenin bir yolundan bahsettiğiniz gibi, alt bileşenlerin çıkışları da bu şekilde kullanılabilir mi?
Mustahsan

5

Bunu ng- dynamic'in dynamicComponent yönergesini kullanarak Angular 2 Final sürümünde çözdük .

Kullanımı:

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

Şablon dinamik şablonunuz ve bağlamınız, şablonunuzun bağlanmasını istediğiniz herhangi bir dinamik veri modeline ayarlanabilir.


AOT ile Açısal 5'i yazarken, JIT derleyicisi pakete dahil olmadığından bunu desteklemez. AOT olmadan bir cazibe gibi çalışır :)
Richard Houltz

bu hala açısal 7+ için de geçerli mi?
Carlos E

4

Radim'in bu mükemmel yazısının üstüne birkaç ayrıntı eklemek istiyorum.

Bu çözümü aldım ve üzerinde biraz çalıştım ve hızlı bir şekilde bazı sınırlamalarla karşılaştım. Ben sadece bunları özetleyeceğim ve daha sonra buna da çözüm vereceğim.

  • Her şeyden önce, dinamik detayı dinamik detay içinde oluşturamıyordum (temelde dinamik UI'leri birbirlerinin içine yerleştirdiler).
  • Bir sonraki sorun, çözümde sunulan parçalardan birinde dinamik bir ayrıntı oluşturmak istediğimdi. Bu ilk çözümle de mümkün değildi.
  • Son olarak, dize düzenleyici gibi dinamik bölümlerde şablon URL'leri kullanmak mümkün değildi.

Bu yazıya, burada bulunabilecek bu sınırlamaların nasıl elde edileceğine dair başka bir soru yaptım:

açısal2 özyinelemeli dinamik şablon derleme

Bu sınırlamaların cevaplarını özetleyeceğim, eğer benimle aynı sorunu yaşarsanız, bu çözümü daha esnek hale getirir. İlk dalma makinesinin de bununla güncellenmesi harika olurdu.

Dinamik ayrıntıların iç içe yerleştirilmesini etkinleştirmek için, type.builder.ts dosyasında içe aktarma ifadesine DynamicModule.forRoot () eklemeniz gerekir.

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

Bunun yanında <dynamic-detail>string-editor veya text-editor olmak üzere parçalardan birinin içinde kullanmak mümkün değildi .

Bunu etkinleştirmek için değiştirmeniz parts.module.tsvedynamic.module.ts

İçinde parts.module.tsSen eklemeniz gerekir DynamicDetailiçindeDYNAMIC_DIRECTIVES

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

Ayrıca dynamic.module.ts, artık parçaların bir parçası oldukları için dynamicDetail'ı kaldırmanız gerekir

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

Çalışan değiştirilmiş bir dalgıç burada bulunabilir: http://plnkr.co/edit/UYnQHF?p=preview (Bu sorunu sadece elçiyim :-D)

Son olarak dinamik bileşenler üzerinde oluşturulan parçalarda şablonurls kullanmak mümkün olmadı. Bir çözüm (veya geçici çözüm. Açısal bir hata mı yoksa çerçevenin yanlış kullanımı mı olduğundan emin değilim), yapıcıda enjekte etmek yerine bir derleyici oluşturmaktı.

    private _compiler;

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

Sonra _compilerderlemek için kullanın, sonra templateUrls de etkinleştirilir.

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

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });

Umarım bu başka birine yardımcı olur!

Saygılarımla Morten


4

Radmin'in mükemmel cevabını takiben, açısal-cli sürüm 1.0.0-beta.22 ve üzerini kullanan herkes için küçük bir değişiklik gerekiyor.

COMPILER_PROVIDERSartık içe aktarılamaz (ayrıntılar için açısal-cli GitHub'a bakın ).

Geçici çözüm Bu yüzden kullanmayın orada COMPILER_PROVIDERSve JitCompileriçinde providersama, kullanımı hiç bölümünden JitCompilerFactoryitibaren '@ açısal / derleyici' yerine tip oluşturucu sınıf içinde böyle:

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

Gördüğünüz gibi, enjekte edilemez ve bu nedenle DI'ye bağımlılığı yoktur. Bu çözüm, açısal-cli kullanmayan projeler için de çalışmalıdır.


1
Bu öneri için teşekkürler, ancak, "DynamicHtmlModule 'için NgModule meta veri bulunamadı" çalıştırıyorum. Uygulamam stackoverflow.com/questions/40060498/…
Cybey

2
Herkes AOT örnek ile JitCompiletFactory çalışıyor mu? @Cybey ile aynı hatayla sahibim
user2771738


2

Kendimi RC4'ü RC5'e nasıl güncelleyebileceğimi görmeye çalışıyorum ve bu yüzden bu girişe tökezledim ve dinamik bileşen oluşturma konusundaki yeni yaklaşım hala benim için biraz gizem taşıyor, bu yüzden bileşen fabrika çözümleyicisi hakkında bir şey önermeyeceğim.

Ancak, ne önerebilirim bu senaryoda bileşen oluşturma için biraz daha açık bir yaklaşım - sadece şablon gibi bazı koşullara göre dize editörü veya metin editörü oluşturacak anahtarı kullanın:

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

Ve bu arada, [prop] ifadesindeki "[" bir anlam ifade eder, bu tek yönlü veri bağlamayı gösterir, bu nedenle özelliği değişkene bağlamanız gerekmediğini biliyorsanız bunları atlayabilirsiniz ve hatta atlamanız gerekir.


1
Eğer switch/ casebirkaç karar içeriyorsa , bu bir yol olacaktır . Ancak, oluşturulan şablonun gerçekten büyük olabileceğini ve her varlık için farklı olabileceğini, güvenlik açısından farklılık gösterebileceğini, varlık durumuna göre, her özellik türüne (sayı, tarih, referans ... editörler) görebileceğini düşünün ... Bu durumda, Bunu html şablonunda çözmek ngSwitchbüyük, çok çok büyük bir htmldosya oluşturur.
Radim Köhler

Sana katılıyorum. Şu anda bu tür bir senaryo var, şu anda gösterilecek belirli sınıf derlemeden önce bilmeden uygulamanın büyük bir bileşeni yüklemeye çalışıyorum. Bu özel durum dinamik bileşen oluşturmaya ihtiyaç duymasa da.
zii

1

Bu, sunucudan oluşturulan dinamik Form denetimlerine örnektir.

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

Bu örnek dinamik Form denetimleri ekleme bileşenidir (Burada Formcontrols sunucudan alabilirsiniz). Addcomponent yöntemini görüyorsanız Forms Control'leri görebilirsiniz. Bu örnekte açısal malzeme kullanmıyorum, ama işe yarıyor (@ work kullanıyorum). Bu, açısal 6'yı hedefler, ancak önceki tüm sürümlerde çalışır.

AngularVersion 5 ve üstü için JITComplierFactory eklemeniz gerekir.

Teşekkürler

Vijay


0

Bu özel durum, bileşeni dinamik olarak oluşturmak için bir yönerge kullanmak gibi görünüyor daha iyi bir seçenek olacaktır. Misal:

Bileşeni oluşturmak istediğiniz HTML'de

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

Direktife şu şekilde yaklaşır ve tasarlarım.

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

Bileşenlerinizde metin, dize, tarih, ne olursa olsun - öğedeki HTML'de geçirdiğiniz yapılandırma ne olursa olsun ng-containerkullanılabilir.

Config,, yourConfigaynı olabilir ve meta verilerinizi tanımlayabilir.

Yapılandırmanıza veya giriş türünüze bağlı olarak, yönerge uygun şekilde ve desteklenen türlerden hareket etmelidir, uygun bileşeni oluşturur. Değilse, bir hata kaydedilecektir.


-1

Ophir Stern'in cevabının üstüne inşa ederek, burada AoT ile Açısal 4'te çalışan bir varyant var. Tek sorunum DynamicComponent'e herhangi bir hizmet enjekte edemiyorum, ancak bununla yaşayabilirim.

not: Açısal 5 ile test etmedim.

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;

  }

}

Bu yardımcı olur umarım.

Şerefe!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.