typescript - Hur kan jag använda/skapa en dynamisk mall för att kompilera en dynamisk komponent med Angular 2.0?


Jag vill skapa en mall dynamiskt. Detta bör användas för att bygga en ComponentType vid körning och placera (även ersätta) den någonstans inuti den värdande komponenten.

Tills RC4 använde jag ComponentResolver, men med RC5 får jag följande meddelande:

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.

Jag hittade detta dokument (Angular 2 Synchronous Dynamic Component Creation)

Och förstå att jag kan använda vilken som helst

  • Typ av dynamisk ngIf med ComponentFactoryResolver. Om jag skickar kända komponenter inuti @Component({entryComponents: [comp1, comp2], ...}) - kan jag använda .resolveComponentFactory(componentToRender);
  • Verklig kompilering vid körning, med Compiler...

Men frågan är hur man använder den Compiler? Anteckningen ovan säger att jag bör kalla: Compiler.compileComponentSync/Async - så hur?

Till exempel. Jag vill skapa (baserat på vissa konfigurationsvillkor) den här typen av mall för en typ av inställningar

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

och i ett annat fall detta (string-editor ersätts med text-editor)

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

Och så vidare (olika nummer/datum/referens redigerare efter egendomstyper, hoppade över vissa egenskaper för vissa användare...). d.v.s. detta är ett exempel, den verkliga konfigurationen kan generera många fler olika och komplexa mallar.

Mallen ändras, så jag kan inte använda ComponentFactoryResolver och skicka befintliga... Jag behöver en lösning med Compiler.

Svar #1

REDIGERA - relaterat till 2.3.0 (2016-12-07)

OBS: för att få lösning för tidigare version, kontrollera historiken för detta inlägg

Liknande ämne diskuteras här: Motsvarighet till $compile i Angular 2. Vi behöver använda JitCompiler och NgModule. Läs mer om NgModule i Angular2 här:

  • Angular 2 RC5 - NgModules, lat inladdning och AoT-kompilering

I korthet

Det finns en fungerande plunker/exempel (dynamisk mall, dynamisk komponenttyp, dynamiskt modul,JitCompiler, ... i bruk)

Huvudprincipen är:
1) skapa mall
2) hitta ComponentFactory i cache - gå till 7)
3) - skapa Component
4) - skapa Module
5) - kompilera Module
6) - returnera (och cacha för senare användning) ComponentFactory
7) använd Target och ComponentFactory för att skapa en instans av dynamisk Component

Här är ett kodavsnitt (mer om det här) - Vår anpassade Builder returnerar bara nyligen byggd/cachad ComponentFactory och vyen Target-platsen använder den för att skapa en instans av 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;
        //...
    });

Det här är det - i korthet. För att få fler detaljer.. läs nedan

.

Sammanfattning

Observera en plunker och återkom för att läsa detaljer om någon kodsnutt behöver mer förklaring

.

Detaljerad förklaring - Angular2 RC6++ & runtime-komponenter

Nedan beskrivning av detta scenario, vi kommer

  1. skapa en modul PartsModule:NgModule (hållare av små delar)
  2. skapa en annan modul DynamicModule:NgModule, som kommer att innehålla vår dynamiska komponent (och referera till PartsModule dynamiskt)
  3. skapa dynamisk mall (enkel metod)
  4. skapa ny Component-typ (endast om mallen har ändrats)
  5. skapa ny RuntimeModule:NgModule. Denna modul kommer att innehålla den tidigare skapade Component-typen
  6. anropa JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) för att få ComponentFactory
  7. skapa en instans av DynamicComponent - jobb för View Target-platsinnehåll och ComponentFactory
  8. tilldela @Inputs till ny instans (växla från INPUT till TEXTAREA-redigering), använd @Outputs

NgModule

Vi behöver ett NgModule.

Även om jag skulle vilja visa ett mycket enkelt exempel, skulle jag i det här fallet behöva tre moduler (faktiskt 4 - men jag räknar inte AppModule). Vänligen, ta detta istället för ett enkelt utdrag som en grund för en riktigt solid generator av dynamiska komponenter.

Det kommer att finnas en modul för alla små komponenter, t.ex. string-editor, text-editor (date-editor, number-editor...)

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

Där DYNAMIC_DIRECTIVES är utökbara och är avsedda att hålla alla små delar som används för vår dynamiska Komponentmall/-typ. Kontrollera app/parts/parts.module.ts

Den andra kommer att vara modul för vår hantering av dynamiska saker. Den kommer att innehålla värdkomponenter och några leverantörer.. som kommer att vara singletons. Därför kommer vi att publicera dem på standard sätt - med 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
            ], 
        };
    }
}

Kontrollera användningen av forRoot() i AppModule

Slutligen kommer vi att behöva en ad hoc-modul vid körning.. men den kommer att skapas senare, som en del av DynamicTypeBuilder-uppgiften.

Den fjärde modulen, applikationsmodulen, är den som deklarerar kompilatorleverantörer:

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

Läs (läs verkligen) mycket mer om NgModule där:

  • Angular 2 RC5 - NgModules, lat inladdning och AoT-kompilering
  • Dokumentation för Angular-moduler

En mall-byggare

I vårt exempel kommer vi att bearbeta detaljer av denna typ av enhet

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

För att skapa en template, använder vi i denna plunker denna enkla/naiva byggare.

Den verkliga lösningen, en riktig mallbyggare, är platsen där din applikation kan göra mycket

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

Ett knep här är - det bygger en mall som använder en uppsättning kända egenskaper, t.ex. entity. Sådana egenskap(er) måste vara en del av den dynamiska komponenten, som vi kommer att skapa nästa.

För att göra det lite enklare kan vi använda ett gränssnitt för att definiera egenskaper, som vår Mallbyggare kan använda. Detta kommer att implementeras av vår dynamiska Komponenttyp.

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

En ComponentFactory-byggare

Mycket viktigt att komma ihåg här är:

vår komponenttyp, byggd med vår DynamicTypeBuilder, kan skilja sig - men bara genom dess mall (skapad ovan). Komponenternas egenskaper (inputs, outputs eller några skyddade) är fortfarande samma. Om vi behöver olika egenskaper bör vi definiera en annan kombination av Mall och Type Builder

Så, vi rör vid kärnan i vår lösning. Byggaren kommer att 1) skapa ComponentType 2) skapa dess NgModule 3) kompilera ComponentFactory 4) cacha den för senare återanvändning.

Ett beroende vi behöver ta emot:

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

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

Och här är ett utdrag om hur man får en 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);
            });
    });
}

Ovan skapar vi och cache både Component och Module. Eftersom om templaten (i själva verket den verkliga dynamiska delen av allt detta) är densamma.. kan vi återanvända

Och här är två metoder, som representerar det riktigt coola sättet att skapa dekorerade klasser/typer i runtime. Inte bara @Component utan också @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;
}

Viktigt:

våra komponenters dynamiska typer skiljer sig, men bara genom mall. Så vi använder det faktum för att cacha dem. Detta är verkligen mycket viktigt. Angular2 kommer också att cacha dessa... efter typen. Och om vi skulle återskapa nya typer för samma mallsträngar... kommer vi börja generera minnesläckor.

ComponentFactory som används av värdkomponenten

Slutstycket är en komponent som är värd för målet för vår dynamiska komponent, t.ex. <div #dynamicContentPlaceHolder></div>. Vi får en referens till det och använder ComponentFactory för att skapa en komponent. Det är i korthet, och här är alla delar av den komponenten (om det behövs, öppna plunker här)

Låt oss först sammanfatta importuttrycken:

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

Vi får bara, mall- och komponentbyggare. Nästa är egenskaper som behövs för vårt exempel (mer i kommentarer)

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

I det här enkla scenariot har vår värdkomponent inga @Input. Så den behöver inte reagera på förändringar. Men trots det faktum (och för att vara redo för kommande förändringar) - behöver vi införa någon flagga om komponenten redan har (först) initierats. Och först då kan vi starta magin.

Slutligen kommer vi att använda vår komponentbyggare, och dess nyligen kompilerade/cachelagda ComponentFactory. Vår Målplatshållare kommer att bli ombedd att instansiera komponenten Component med den fabriken.

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

litet tillägg

Vi behöver också hålla en referens till den kompilerade mallen för att kunna destroy() den på rätt sätt, när vi än kommer att ändra den.

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

klar

Det är i princip allt. Glöm inte att förstöra allt som byggdes dynamiskt (ngOnDestroy). Se också till att cacha dynamiska typer och moduler om den enda skillnaden är deras mall.

Kolla allt i aktion här

för att se tidigare versioner (t.ex. RC5 relaterade) av detta inlägg, kolla historiken

Svar #2

REDIGERA (26/08/2017): Lösningen nedan fungerar bra med Angular2 och 4. Jag har uppdaterat den för att innehålla en mallvariabel och en klickhanterare och testat den med Angular 4.3.
För Angular4 är ngComponentOutlet som beskrivs i Ophirs svar en mycket bättre lösning. Men just nu stöder det inte ännu inputs & outputs. Om [denna PR](https://github.com/angular/angular/pull/15362) accepteras, skulle det vara möjligt genom komponentinstansen som returneras av create-händelsen.
ng-dynamic-component kan vara den bästa och enklaste lösningen totalt sett, men jag har inte testat det än.

@Long Fields svar är helt rätt! Här är ett annat (synkront) exempel:

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

Live på http://plnkr.co/edit/fdP9Oc.

Svar #3

Jag måste ha kommit sent till festen, ingen av lösningarna här verkade hjälpsamma för mig - för röriga och kändes som för mycket av en tillfällig lösning.

Vad jag slutade med att göra var att använda Angular 4.0.0-beta.6's ngComponentOutlet.

Det gav mig den kortaste, enklaste lösningen, allt skrivet i den dynamiska komponentens fil.

  • Här är ett enkelt exempel som bara tar emot text och placerar den i en mall, men uppenbarligen kan du ändra det efter dina behov:
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;
  }
}
  • Kort förklaring:

    1. my-component - komponenten där en dynamisk komponent renderas
    2. DynamicComponent - komponenten som ska byggas dynamiskt och den renderas inuti my-component

Glöm inte att uppgradera alla Angular-bibliotek till ^Angular 4.0.0

Hoppas detta hjälper, lycka till!

UPPDATERING

Fungerar också för Angular 5.

Svar #4

2019 juni svar

Fantastiska nyheter! Det verkar som att @angular/cdk-paketet nu har förstklassigt stöd för portar!

Vid tidpunkten för skrivandet fann jag inte ovanstående officiella dokument särskilt hjälpsamma (särskilt när det gäller att skicka data till och ta emot händelser från de dynamiska komponenterna). Sammanfattningsvis kommer du att behöva:

Steg 1) Uppdatera din AppModule

Importera PortalModule från paketet @angular/cdk/portal och registrera dina dynamiska komponent(er) i entryComponents

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

Steg 2. Alternativ A: Om du INTE behöver skicka data till och ta emot händelser från dina dynamiska komponenter:

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

Se det i aktion

Steg 2. Alternativ B: Om du BEHÖVER skicka data till och ta emot händelser från dina dynamiska komponenter:

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

Se det i aktion

Svar #5

Jag bestämde mig för att komprimera allt jag lärt mig till en fil. Det är mycket att ta in här, särskilt jämfört med innan RC5. Observera att denna källfil inkluderar AppModule och 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 {}`

Svar #6

Jag har ett enkelt exempel för att visa hur man gör en dynamisk komponent i Angular 2 rc6.

Säg att du har en dynamisk HTML-mall=template1 och vill ladda dynamiskt, först packa in den i en komponent

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

här template1 som html, kan innehålla ng2-komponent

Från rc6 måste man ha @NgModule som omsluter denna komponent. @NgModule, precis som modul i AngularJS 1, kopplar loss olika delar av ng2-applikationen, så:

@Component({
  template: template1,

})
class DynamicComponent {

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

(Här importerar du RouterModule som i mitt exempel, det finns några ruttkomponenter i min HTML som du kan se senare)

Nu kan du kompilera DynamicModule som: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

Och vi behöver lägga ovanstående i app.moudule.ts för att ladda det, vänligen se min app.moudle.ts. För mer och fullständiga detaljer, kolla: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts och app.moudle.ts

och se demo: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview

Relaterade artiklar