Jak mogę użyć / utworzyć dynamiczny szablon do kompilacji dynamicznego komponentu z Angular 2.0?

Chcę dynamicznie tworzyć szablon. To powinno być użyte do zbudowania ComponentType w czasie wykonywania i umieszczenia (nawet zastąpienia) gdzieś wewnątrz komponentu hostingowego.

Do RC4 używałem ComponentResolver, ale z RC5 otrzymuję następujący komunikat:

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.

Znalazłem ten dokument (Angular 2 Synchronous Dynamic Component Creation )

I zrozumieć, że mogę użyć albo

  • rodzaj dynamiki ngIf z ComponentFactoryResolver. If I przekazać znane komponenty wewnątrz @Component({entryComponents: [comp1, comp2], ...}) - mogę użyć .resolveComponentFactory(componentToRender);
  • Real runtime compilation, with Compiler...

Ale pytanie brzmi, jak tego użyć Compiler? Notka powyżej mówi, że powinienem zadzwonić: Compiler.compileComponentSync/Async - więc jak?

Na przykład. Chcę stworzyć (na podstawie pewnych warunków konfiguracyjnych) tego rodzaju szablon dla jednego rodzaju ustawień

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

A w innym przypadku to (string-editor zastępuje się text-editor)

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

I tak dalej (różne liczby/daty / odniesienia editors według typów właściwości, pominięte niektóre właściwości dla niektórych użytkowników...). czyli jest to przykład, prawdziwa konfiguracja może generować znacznie bardziej różne i złożone szablony.

Szablon się zmienia, więc nie mogę użyć ComponentFactoryResolver i przekazać istniejących... Potrzebuję rozwiązania z Compiler.

Author: Edric, 2016-08-11

15 answers

Edycja-związana z 2.3.0 (2016-12-07)

UWAGA: Aby uzyskać rozwiązanie dla poprzedniej wersji, Sprawdź historię tego postu

Podobny temat jest omawiany tutaj odpowiednik $compile w Angular 2 . Musimy użyć JitCompiler i NgModule. Czytaj więcej o NgModule w Angular2 tutaj:

W skrócie

Jest roboczy plunker / przykład (dynamic template, Dynamic component type, dynamic module, JitCompiler,... w akcji)

Głównym jest:
1) Utwórz szablon
2) znajdź ComponentFactory W cache - przejdź do 7)
3) - Utwórz Component
4) - Utwórz Module
5) - compile Module
6) - return (i cache do późniejszego wykorzystania) ComponentFactory
7) użyj Target i ComponentFactory aby utworzyć Przykład dynamiki Component

Oto fragment kodu (więcej tutaj) - nasz Niestandardowy Konstruktor zwraca właśnie built / cached ComponentFactory, a obiekt zastępczy view target zużywa się, aby utworzyć instancję 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;
        //...
    });

To jest to-w skrócie to. Aby uzyskać więcej szczegółów.. czytaj poniżej

.

TL&DR

Obserwuj plunkera i wróć, aby przeczytać szczegóły, jeśli jakiś fragment wymaga więcej Wyjaśnienie

.

Szczegółowe wyjaśnienie-Angular2 RC6++ & komponenty runtime

Poniżej opis tego scenariusza , będziemy

  1. tworzenie modułu PartsModule:NgModule (uchwyt na małe kawałki)
  2. utwórz kolejny moduł DynamicModule:NgModule, który będzie zawierał nasz dynamiczny komponent (i odniesienie PartsModule dynamicznie)
  3. tworzenie dynamicznego szablonu (proste podejście)
  4. create new Component type (tylko jeśli szablon się zmienił)
  5. utwórz nowy RuntimeModule:NgModule. Moduł ten będzie zawierał wcześniej utworzony typ Component
  6. zadzwoń JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) aby uzyskać ComponentFactory
  7. tworzenie instancji DynamicComponent - zadania elementu zastępczego widoku docelowego i ComponentFactory
  8. przypisanie @Inputs do nowej instancji (Przełącz z INPUT na TEXTAREA edycja) , używaj @Outputs

NgModule

Potrzebujemy NgModule s.

Podczas gdy chciałbym pokazać bardzo prosty przykład, w tym przypadku potrzebowałbym trzech modułów (w rzeczywistości 4-ale nie liczę AppModule) . Proszę, weź ten zamiast zwykłego fragmentu jako podstawę naprawdę solidnego generatora komponentów dynamicznych.

Będzie jeden moduł dla wszystkich małych komponentów, np. string-editor, text-editor (date-editor, number-editor...)

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

Gdzie DYNAMIC_DIRECTIVES są rozszerzalne i przeznaczone są do przechowywania wszystkich małych części używanych do naszych dynamiczny szablon/typ komponentu. Sprawdź app / parts / parts.moduł.ts

Drugi będzie modułem do dynamicznej obsługi rzeczy. Będzie zawierał komponenty hostingowe i niektórych dostawców.. które będą singletonami. Dlatego opublikujemy je w standardowy sposób-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
            ], 
        };
    }
}

Sprawdź użycie forRoot() w AppModule

Na koniec będziemy potrzebować modułu adhoc, runtime.. ale to zostanie utworzone później, jako część DynamicTypeBuilder praca.

Moduł forth, moduł aplikacji, jest Tym, który utrzymuje dostawców kompilatorów:

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

Czytaj (Czytaj) dużo więcej o NgModule tam:

A szablon budowniczy

W naszym przykładzie będziemy przetwarzać szczegóły tego rodzaju bytu

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

To Utwórz template, w tym plunker {[74] } używamy tego prostego / naiwnego budowniczego.

realne rozwiązanie, prawdziwy Kreator szablonów, to miejsce, w którym Twoja aplikacja może wiele zrobić]}

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

Oto sztuczka - buduje szablon, który wykorzystuje pewien zestaw znanych właściwości, np. entity. Taka właściwość(-ies) musi być częścią komponentu dynamicznego, który utworzymy następnie.

Aby było to nieco łatwiejsze, możemy użyć interfejsu do zdefiniowania właściwości, z których może korzystać nasz kreator szablonów. Zostanie to zaimplementowane przez nasz typ komponentu dynamicznego.

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

A ComponentFactory budowniczy

Bardzo ważną rzeczą jest, aby pamiętać:

Nasz typ komponentu, zbudowany z naszego DynamicTypeBuilder, może się różnić - ale tylko przez jego szablon (utworzony powyżej) . Właściwości komponentów (wejścia, wyjścia lub niektóre chronione) są nadal takie same. jeśli potrzebujemy różnych właściwości, powinniśmy zdefiniować różne kombinacje Szablon i typ Builder

Więc dotykamy sedna naszego rozwiązania. Builder, will 1) create ComponentType 2) create its NgModule 3) compile ComponentFactory 4) pamięć podręczna to do późniejszego ponownego użycia.

Zależność, którą musimy otrzymać:

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

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

A oto fragment jak zdobyć 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);
            });
    });
}

Powyżej tworzymy i cache zarówno Component jak i Module. Ponieważ jeśli szablon (w rzeczywistości prawdziwa dynamiczna część tego wszystkiego) jest to samo.. możemy ponownie użyć

A oto dwie metody, które reprezentują naprawdę fajny sposób tworzenia zdobionych klas/typów w runtime. Nie tylko @Component ale także @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;
}

Ważne:

Nasze typy dynamiczne komponentów różnią się, ale tylko według szablonu. Więc używamy tego faktu do ich buforowania . To naprawdę bardzo ważne. Angular2 będzie również buforować te.. według typu . I gdybyśmy odtworzyli dla tego samego ciągi szablonów nowe typy... zaczniemy generować wycieki pamięci.

ComponentFactory używany przez komponent hostingowy

Ostatni element jest komponentem, który hostuje cel dla naszego komponentu dynamicznego, np. <div #dynamicContentPlaceHolder></div>. Otrzymujemy odniesienie do niego i używamy ComponentFactory do stworzenia komponentu. To jest w skrócie, a tutaj są wszystkie elementy tego komponentu (w razie potrzeby otwórz plunker tutaj)

Najpierw podsumujmy instrukcje importu:

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

Po prostu otrzymuj, szablony i konstruktory komponentów. Następnie są właściwości, które są potrzebne dla naszego przykładu (więcej w komentarzach)

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

W tym prostym scenariuszu nasz komponent hostingowy nie ma żadnego @Input. Nie musi więc reagować na zmiany. Ale pomimo tego (i aby być gotowym na nadchodzące zmiany) - musimy wprowadzić jakiś znacznik, jeśli komponent był już (po pierwsze) {79]} zainicjowany. I tylko wtedy możemy zacząć magię.

Wreszcie użyjemy nasz konstruktor komponentów i jego właśnie skompilowany/buforowany ComponentFacotry. Nasz Target placeholder zostanie poproszony o utworzenie Component z tą fabryką.

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

Małe rozszerzenie

Ponadto, musimy zachować odniesienie do skompilowanego szablonu.. aby móc właściwie destroy() to, kiedy tylko to zmienimy.

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

Zrobione

To tyle. Nie zapomnij zniszczyć wszystkiego, co zostało zbudowane dynamicznie (ngOnDestroy) . Pamiętaj również, aby cache dynamic types i modules, jeśli jedyną różnicą jest ich szablon.

Sprawdź wszystko w akcji tutaj

Aby zobaczyć poprzednie wersje (np. związane z RC5) tego postu, sprawdź historię

 165
Author: Radim Köhler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

EDIT (26/08/2017) : poniższe rozwiązanie działa dobrze z Angular2 i 4. Zaktualizowałem go, aby zawierał zmienną szablonu i kliknąłem handler i Przetestowałem go z Angular 4.3.
Dla Angular4, ngComponentOutlet opisany w Ophir ' s answer jest znacznie lepszym rozwiązaniem. Ale obecnie nie obsługuje jeszcze wejść i wyjść . If [this PR] ( https://github.com/angular/angular/pull/15362] jest akceptowane, byłoby to możliwe poprzez zwracaną instancję komponentu za pomocą zdarzenia Utwórz.
ng-dynamic-component może być najlepszym i najprostszym rozwiązaniem, ale jeszcze tego nie testowałem.

@ Long Field ' s answer is spot on! Oto kolejny (synchroniczny) przykład:

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 at http://plnkr.co/edit/fdP9Oc .

 58
Author: Rene Hamburger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-26 08:22:16

Musiałem przybyć na imprezę późno, żadne z rozwiązań tutaj nie wydawało mi się pomocne - zbyt bałagan i czułem się jak zbyt wiele obejścia.

Skończyło się na użyciu Angular 4.0.0-beta.6's ngComponentOutlet .

To dało mi najkrótsze i najprostsze rozwiązanie zapisane w pliku komponentu dynamicznego.

  • Oto prosty przykład, który po prostu odbiera tekst i umieszcza go w szablonie, ale oczywiście można zmienić zgodnie z Twoim potrzeba:
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;
  }
}
  • krótkie wyjaśnienie:
    1. my-component - komponent, w którym komponent dynamiczny jest renderowany
    2. DynamicComponent - komponent do dynamicznej budowy i renderowania wewnątrz my-component

Nie zapomnij uaktualnić wszystkich bibliotek angular do ^Angular 4.0.0

Mam nadzieję, że to pomoże, powodzenia!

UPDATE

Działa również na angular 5.

 52
Author: Ophir Stern,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-07 21:04:42

2019 czerwcowa odpowiedź

Świetne wieści! Wygląda na to, że pakiet @ angular / cdk ma teraz pierwszorzędne wsparcie dla portali !

W momencie pisania tego artykułu nie uznałem powyższych oficjalnych dokumentów za szczególnie pomocne (szczególnie w odniesieniu do wysyłania danych do i odbierania zdarzeń z komponentów dynamicznych). Podsumowując, będziesz musiał:

Krok 1) Zaktualizuj swój AppModule

Importuj PortalModule z pakietu @angular/cdk/portal i zarejestruj Twoje komponenty dynamiczne wewnątrz entryComponents

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

Punkt 2. Opcja A: jeśli nie musisz przekazywać danych do zdarzeń i odbierać ich z komponentów dynamicznych:

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

Zobacz to w akcji

Punkt 2. Opcja B: jeśli musisz przekazywać dane do zdarzeń i odbierać je z komponentów dynamicznych:

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

Zobacz to w akcji

 21
Author: Stephen Paul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-27 16:53:51

Postanowiłem skompaktować wszystko, czego się nauczyłem w jeden plik. Jest tu dużo do wzięcia, szczególnie w porównaniu z przed RC5. Zauważ, że ten plik źródłowy zawiera AppModule i 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 {}`
 18
Author: Stephen Paul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-04 08:52:32

Mam prosty przykład, aby pokazać, jak zrobić angular 2 RC6 dynamic component.

Powiedzmy, że masz dynamiczny szablon html = template1 i chcesz dynamicznie ładować, najpierw zawiń w komponent

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

Tutaj template1 jako html, może zawierać komponent ng2

Z rc6, musi mieć @NgModule zawijać ten komponent. @NgModule, podobnie jak moduł w anglarJS 1, odsprzęga on inną część aplikacji ng2, więc:

@Component({
  template: template1,

})
class DynamicComponent {

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

(tutaj import RouterModule jak w moim przykładzie tam czy niektóre komponenty trasy w moim html, jak widać później)

Teraz możesz skompilować DynamicModule jako: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

I musimy umieścić powyżej w aplikacji.moudule.ts, aby go załadować, zobacz moją aplikację.moudle.ts. Aby uzyskać więcej i pełne szczegóły sprawdź: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts i app.moudle.ts

I zobacz demo: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview

 10
Author: Long Field,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-21 04:00:36

W kątowej 7.x użyłem do tego elementów kątowych.

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

  2. Utwórz usługę akcesoriów.

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

Zauważ, że niestandardowy znacznik elementu musi się różnić od selektora elementu kątowego. w AppUserIconComponent:

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

I w tym przypadku niestandardowa nazwa tagu użyłem "user-icon".

  1. Następnie należy wywołać register w AppComponent:
@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {
  constructor(   
    dynamicComponents: DynamicComponentsService,
  ) {
    dynamicComponents.register();
  }

}
  1. a teraz w każdym miejsce Twojego kodu możesz użyć go w następujący sposób:
dynamicComponents.create('user-icon', {user:{...}});

Lub w ten sposób:

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

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

(w szablonie):

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

Zauważ, że w drugim przypadku musisz przekazać obiekty z JSON.stringify, a następnie parse go ponownie. Nie mogę znaleźć lepszego rozwiązania.

 6
Author: Oleg Pnk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-14 10:44:42

Rozwiązano to w ostatecznej wersji Angular 2 po prostu za pomocą dyrektywy dynamicComponent z ng-dynamic.

Użycie:

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

Gdzie szablon jest dynamicznym szablonem, a kontekst można ustawić na dowolny dynamiczny model danych, z którym chcesz powiązać szablon.

 5
Author: Richard Houltz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-26 18:32:17

Chcę dodać kilka szczegółów na górze tego bardzo doskonałego postu przez Radim.

Wziąłem to rozwiązanie i trochę nad nim pracowałem i szybko napotkałem pewne ograniczenia. Po prostu je zarysuję, a następnie dam rozwiązanie tego.

  • Po pierwsze nie byłem w stanie renderować dynamicznych detali wewnątrz Dynamic-detail (zasadniczo nest dynamic Ui wewnątrz siebie).
  • następnym problemem było to, że chciałem renderować dynamiczny detal wewnątrz jedną z części, która była udostępniony w rozwiązaniu. To było nie jest to również możliwe w przypadku początkowego rozwiązania.
  • wreszcie nie było możliwe użycie adresów URL szablonów w dynamicznych częściach, takich jak edytor łańcuchów.

Na podstawie tego postu zadałem kolejne pytanie, jak osiągnąć te ograniczenia, które można znaleźć tutaj:

Rekurencyjna dynamiczna kompilacja szablonów w angular2

Po prostu przedstawię odpowiedzi na te ograniczenia, jeśli napotkasz ten sam problem, co ja, ponieważ to sprawia, że rozwiązanie jest bardziej elastyczne. Byłoby wspaniale, aby początkowy plunker został zaktualizowany również o to.

Aby włączyć zagnieżdżanie dynamicznych detali wewnątrz siebie, musisz dodać DynamicModule.forRoot() w instrukcji import w typie .budowniczy.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;
}

Poza tym nie było możliwe użycie <dynamic-detail> wewnątrz jednej z części będących edytorem łańcuchów lub edytorem tekstu.

Aby to włączyć należy zmienić parts.module.ts i dynamic.module.ts

Wewnątrz parts.module.ts musisz dodać DynamicDetail w DYNAMIC_DIRECTIVES

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

Również w dynamic.module.ts będziesz musiał usunąć dynamicDetail, ponieważ są one teraz częścią części

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

Działający zmodyfikowany plunker można znaleźć tutaj: http://plnkr.co/edit/UYnQHF?p=preview (nie rozwiązałem tego problemu, jestem tylko posłańcem : - d)

Ostatecznie nie było możliwe użycie szablonów w częściach utworzonych na komponentach dynamicznych. Rozwiązanie (lub obejście. Nie jestem pewnie, czy to błąd kątowy, czy niewłaściwe użycie frameworka) było stworzenie kompilatora w konstruktorze zamiast wstrzykiwania go.

    private _compiler;

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

Następnie użyj _compiler aby skompilować, następnie włączane są również szablony.

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

                this._cacheOfFactories[template] = factory;

                resolve(factory);
            });
    });

Mam nadzieję, że to pomoże komuś innemu!

Pozdrawiam Morten

 4
Author: Morten Skjoldager,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:33

Podążając za doskonałą odpowiedzią Radmina, jest trochę poprawek potrzebnych dla wszystkich, którzy używają angular-CLI w wersji 1.0.0-beta.22 i więcej.

COMPILER_PROVIDERSnie można już importować (szczegóły w angular-Cli GitHub ).

Więc obejście nie powinno w ogóle używać COMPILER_PROVIDERS i JitCompiler w sekcji providers, ale używać JitCompilerFactory z '@ angular / compiler ' zamiast tego w ten sposób wewnątrz klasy type builder:

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

Jak widać, nie jest wstrzykiwany i w związku z tym nie ma zależności od DI. To rozwiązanie powinno działać również w przypadku projektów nie korzystających z angular-cli.

 4
Author: Sebastian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-04 09:26:12

Sam staram się zobaczyć jak mogę zaktualizować RC4 do RC5 i dlatego natknąłem się na ten wpis i nowe podejście do dynamicznego tworzenia komponentów nadal kryje dla mnie trochę tajemnicy, więc nie będę sugerował niczego na component factory resolver.

Ale mogę zasugerować nieco jaśniejsze podejście do tworzenia komponentów w tym scenariuszu - wystarczy użyć przełącznika w szablonie, który utworzy edytor tekstu lub edytor tekstu zgodnie z pewnym warunkiem, jak to:

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

I przy okazji, " ["w [prop] wyrażenie ma znaczenie, oznacza to jednokierunkowe powiązanie danych, dlatego możesz, a nawet powinieneś je pominąć w przypadku, gdy wiesz, że nie musisz wiązać właściwości ze zmienną.

 2
Author: zii,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-11 07:31:45

Jest to przykład dynamicznej kontroli formularzy generowanej z serwera.

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

Ten przykład jest dynamiczny Form controls znajduje się w add component (tutaj można pobrać Formcontrols z serwera). Jeśli widzisz metodę addcomponent, możesz zobaczyć kontrolki formularzy. W tym przykładzie nie używam materiału kątowego, ale działa (używam @ work). Jest to cel do angular 6, ale działa we wszystkich poprzednich wersjach.

Need to dodaj JITComplierFactory dla AngularVersion 5 i nowszych.

Thanks

Vijay

 2
Author: Vijay Anand Kannan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-05 12:24:01

Jeśli wszystko, czego potrzebujesz, aby przetworzyć dynamiczny ciąg znaków i załadować komponenty przez ich selektory, możesz również uznać bibliotekę ngx-dynamic-hooks za przydatną. Początkowo stworzyłem to jako część osobistego projektu, ale nie widziałem czegoś podobnego, więc trochę go dopracowałem i upubliczniłem.

Niektóre Ciekawostki:

  • możesz załadować dowolne komponenty do dynamicznego ciągu za pomocą ich selektora (lub dowolnego innego wybranego wzoru!)
  • wejścia i wyjścia mogą być tylko se jak w normalnym szablonie
  • komponenty mogą być zagnieżdżane bez ograniczeń
  • możesz przekazywać dane aktywne z komponentu nadrzędnego do dynamicznie ładowanych komponentów (a nawet używać ich do wiązania wejść/wyjść)
  • możesz kontrolować, które komponenty mogą być ładowane w każdym wyjściu, a nawet jakie wejścia/wyjścia możesz im dać
  • biblioteka używa wbudowanego Domsanitizer Angular, aby być bezpiecznym w użyciu nawet z potencjalnie niebezpiecznymi wejściami.

W szczególności, to robi nie polegaj na kompilatorze runtime, tak jak niektóre inne odpowiedzi tutaj. Z tego powodu nie można używać składni szablonu. Z drugiej strony oznacza to, że działa zarówno w trybach JiT, jak i AOT, a także zarówno Ivy, jak i stary silnik szablonów, a także jest znacznie bezpieczniejszy w użyciu w ogóle.

Zobacz go w akcji W tym Stackblitz .

 1
Author: Mvin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-26 06:54:35

W tym konkretnym przypadku lepiej byłoby użyć dyrektywy do dynamicznego tworzenia komponentu. Przykład:

W HTML, w którym chcesz utworzyć komponent

<ng-container dynamicComponentDirective [someConfig]="someConfig"></ng-container>
[4]}podchodziłbym do dyrektywy i projektował ją w następujący sposób.
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;
    }
}

Więc w Twoich komponentach tekst, ciąg, data, cokolwiek - cokolwiek config przekazałeś w HTML w elemencie ng-container będzie dostępny.

Config, yourConfig, może być taki sam i definiować Twoje metadane.

W zależności od konfiguracji lub typu danych wejściowych dyrektywa powinna działać odpowiednio i z obsługiwanych typów renderuje odpowiedni komponent. Jeśli nie, zarejestruje błąd.

 0
Author: saidutt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-20 20:15:04

Jest to jedna z najbardziej znanych postaci w grze. Jedynym problemem jest to, że nie mogę wprowadzić żadnych usług do DynamicComponent, ale mogę z tym żyć.

Uwaga: nie testowałem z 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;

  }

}
Mam nadzieję, że to pomoże. Zdrówko!
 -1
Author: Spitfire,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-16 12:41:21