227 votes

Comment puis-je utiliser/créer un modèle dynamique pour compiler un composant dynamique avec Angular 2.0 ?

Je veux créer dynamiquement un modèle. Celui-ci doit être utilisé pour construire un ComponentType au moment de l'exécution et de placer (même remplacer) à l'intérieur du composant d'hébergement.

Jusqu'à la RC4, j'utilisais ComponentResolver mais avec la RC5, je reçois le message suivant :

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.

J'ai trouvé ce document ( Création de composants dynamiques synchrones en Angular 2 )

Et comprenez que je peux utiliser soit

  • Une sorte de dynamique ngIf avec ComponentFactoryResolver . Si je passe des composants connus à l'intérieur de @Component({entryComponents: [comp1, comp2], ...}) - Je peux utiliser .resolveComponentFactory(componentToRender);
  • Compilation en temps réel, avec Compiler ...

Mais la question est de savoir comment utiliser cette Compiler ? La note ci-dessus indique que je dois appeler : Compiler.compileComponentSync/Async - Alors comment ?

Par exemple. Je veux créer (en fonction de certaines conditions de configuration) ce type de modèle pour un type de paramètres

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

et dans un autre cas, celui-ci ( string-editor est remplacé par text-editor )

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

Et ainsi de suite (numéro différent/date/référence editors par types de propriétés, a ignoré certaines propriétés pour certains utilisateurs...) . c'est-à-dire qu'il s'agit d'un exemple, la configuration réelle pourrait générer des modèles beaucoup plus différents et complexes.

Le modèle est en train de changer, je ne peux donc pas utiliser ComponentFactoryResolver et passer ceux qui existent déjà... J'ai besoin d'une solution avec le Compiler .

0 votes

Puisque la solution que j'ai trouvée était si bonne, je veux que tous ceux qui trouvent cette question jettent un coup d'œil à ma réponse qui se trouve tout en bas de la page :)

0 votes

L'article Voici ce que vous devez savoir sur les composants dynamiques en Angular a une excellente explication des composants dynamiques.

1 votes

C'est le problème avec toutes les réponses qui existent et ce que $compile Je suis en train de créer une application où je veux juste compiler le HTML tel qu'il arrive à travers la page d'un tiers et les appels ajax. Je ne peux pas retirer le HTML de la page et le placer dans mon propre modèle. Sigh

174voto

Radim Köhler Points 26836

EDIT - lié à 2.3.0 (2016-12-07)

REMARQUE : pour obtenir la solution d'une version antérieure, consultez l'historique de cet article.

Un sujet similaire est discuté ici Equivalent de $compile en Angular 2 . Nous devons utiliser JitCompiler y NgModule . Lire la suite NgModule en Angular2 ici :

En bref

Il y a un plunker/exemple fonctionnel (modèle dynamique, type de composant dynamique, module dynamique, JitCompiler , ... en action)

Le principal est :
1) créer un modèle
2) trouver ComponentFactory dans le cache - aller à 7)
3) - créer Component
4) - créer Module
5) - compiler Module
6) - retour (et mise en cache pour une utilisation ultérieure) ComponentFactory
7) utiliser Cible y ComponentFactory pour créer une instance de dynamique Component

Voici un extrait de code (en savoir plus aquí ) - Notre Builder personnalisé renvoie les données construites/cachées. ComponentFactory et le placeholder de la vue Target consomment pour créer une instance de l'application 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;
        //...
    });

C'est tout - en bref. Pour obtenir plus de détails, lisez ce qui suit

.

TL&DR

Observez un plunker et revenez lire les détails au cas où un extrait nécessiterait plus d'explications.

.

Explication détaillée - Angular2 RC6++ & composants d'exécution

Description ci-dessous de ce scénario nous allons

  1. créer un module PartsModule:NgModule (support de petits morceaux)
  2. créer un autre module DynamicModule:NgModule qui contiendra notre composant dynamique (et la référence PartsModule dynamiquement)
  3. créer un modèle dynamique (approche simple)
  4. créer un nouveau Component type (seulement si le modèle a changé)
  5. créer un nouveau RuntimeModule:NgModule . Ce module contiendra les Component type
  6. appelez JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) pour obtenir ComponentFactory
  7. créer une instance de la DynamicComponent - de l'espace réservé à la cible de vue et ComponentFactory
  8. attribuer @Inputs a nouvelle instance (passer de INPUT a TEXTAREA édition) , consommer @Outputs

NgModule

Nous avons besoin d'un NgModule s.

Bien que je souhaite montrer un exemple très simple, dans ce cas, j'aurais besoin de trois modules (en fait 4 - mais je ne compte pas le AppModule) . S'il vous plaît, prenez ceci plutôt qu'un simple extrait comme base d'un générateur de composants dynamiques vraiment solide.

Il y aura un pour tous les petits composants, par 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ónde DYNAMIC_DIRECTIVES sont extensibles et sont destinés à contenir toutes les petites pièces utilisées pour notre modèle/type de composant dynamique. Consultez app/parts/parts.module.ts

Le second sera un module pour la gestion de notre substance dynamique. Il contiendra des composants d'hébergement et quelques fournisseurs qui seront des singletons. Par conséquent, nous les publierons de manière standard - avec 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
            ], 
        };
    }
}

Vérifiez l'utilisation de la forRoot() dans le AppModule

Enfin, nous aurons besoin d'un module d'exécution adhoc, mais il sera créé plus tard, dans le cadre de l'initiative de l'UE. DynamicTypeBuilder travail.

Le quatrième module, le module d'application, est celui qui maintient les déclarations des fournisseurs du compilateur :

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

Lire (faire lire) beaucoup plus sur NgModule là :

A modèle constructeur

Dans notre exemple, nous traiterons le détail de ce type de entité

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

Pour créer un template dans ce plunker nous utilisons ce constructeur simple/naïf.

La vraie solution, un vrai constructeur de modèles, est l'endroit où votre application peut en faire beaucoup

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

L'astuce consiste à construire un modèle qui utilise un ensemble de propriétés connues, par ex. entity . Ces propriétés doivent faire partie d'un composant dynamique, que nous allons créer ensuite.

Pour rendre les choses un peu plus faciles, nous pouvons utiliser une interface pour définir les propriétés que notre constructeur de modèles peut utiliser. Ceci sera implémenté par notre type de composant dynamique.

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

A ComponentFactory constructeur

Il est très important de garder à l'esprit ce qui suit :

notre type de composant, construire avec notre DynamicTypeBuilder pourrait différer - mais seulement par son modèle (créé ci-dessus) . Propriétés des composants (entrées, sorties ou certains protégé) sont toujours les mêmes. Si nous avons besoin de propriétés différentes, nous devons définir une combinaison différente de Template et de Type Builder.

Nous touchons donc au cœur de notre solution. Le Builder, va 1) créer ComponentType 2) créer son NgModule 3) compiler ComponentFactory 4) cache pour une réutilisation ultérieure.

Une dépendance que nous devons recevoir :

// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';

@Injectable()
export class DynamicTypeBuilder {

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

Et voici un extrait de comment obtenir un 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);
            });
    });
}

Au-dessus, nous créons et cache les deux Component y Module . Parce que si le modèle (en fait, la partie vraiment dynamique de tout cela) est le même nous pouvons réutiliser

Et voici deux méthodes, qui représentent la manière vraiment cool de créer une décoré classes/types en cours d'exécution. Non seulement @Component mais aussi le @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;
}

Important :

les types dynamiques de nos composants diffèrent, mais seulement par modèle. Nous utilisons donc ce fait pour le cache les. C'est vraiment très important. Angular2 mettra également en cache ces par le type . Et si nous recréons pour les mêmes chaînes de modèles de nouveaux types... nous commencerons à générer des fuites de mémoire.

ComponentFactory utilisé par le composant d'hébergement

La pièce finale est un composant, qui héberge la cible de notre composant dynamique, par ex. <div #dynamicContentPlaceHolder></div> . Nous obtenons une référence à celui-ci et utilisons ComponentFactory pour créer un composant. En résumé, voici toutes les pièces de ce composant (si nécessaire, ouvrir plunker ici )

Résumons tout d'abord les déclarations d'importation :

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

Nous ne recevons que des constructeurs de modèles et de composants. Les propriétés suivantes sont nécessaires pour notre exemple (plus dans les commentaires)

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

Dans ce scénario simple, notre composant d'hébergement ne dispose pas de @Input . Il n'a donc pas à réagir aux changements. Mais malgré ce fait (et être prêt pour les changements à venir) - nous devons introduire un indicateur si le composant a déjà été (premièrement) initié. Et seulement alors nous pourrons commencer la magie.

Enfin, nous allons utiliser notre constructeur de composants, et son juste compilé/caché ComponentFacotry . Notre site Emplacement de la cible sera demandé d'instancier el Component avec cette usine.

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

petite extension

De plus, nous devons garder une référence au modèle compilé pour pouvoir correctement destroy() il, quand nous le changerons.

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

fait

C'est à peu près tout. N'oubliez pas de Détruire tout ce qui a été construit dynamiquement (ngOnDestroy) . Veillez également à cache dynamique types y modules si la seule différence est leur modèle.

Regardez tout cela en action aquí

pour voir les versions précédentes (par exemple, lié à RC5) de ce billet, consultez le histoire

1 votes

Je vois que vous utilisez toujours le directives mot-clé dans @component ( type.builder.ts ). Cela n'est-il pas déprécié depuis la RC5 ? Si oui, existe-t-il une solution pour éviter cela ? Je pense mettre @ngmodule dans le app/parts/*.ts mais je n'ai pas encore réussi à le faire...

0 votes

Adapté le plunker de @Radim pour utiliser la génération dynamique de modules. Il serait probablement plus joli de créer des modules séparés pour les deux directives et d'en importer un seul de manière sélective... qu'en pensez-vous ? plnkr.co/edit/Mrh3CL5qBABkUGl9DU8H?p=preview

56 votes

Cela semble être une solution très compliquée, la solution obsolète était très simple et claire, y a-t-il un autre moyen de faire cela ?

59voto

Rene Hamburger Points 981

MODIFIER (26/08/2017) : La solution ci-dessous fonctionne bien avec Angular2 et 4. Je l'ai mise à jour pour contenir une variable de modèle et un gestionnaire de clics et je l'ai testée avec Angular 4.3.
Pour Angular4, ngComponentOutlet comme décrit dans Réponse d'Ophir est une bien meilleure solution. Mais pour l'instant, il ne prend pas en charge les entrées et les sorties encore. Si [cette RP]( [https://github.com/angular/angular/pull/15362\]](https://github.com/angular/angular/pull/15362]) est accepté, il serait possible d'utiliser l'instance du composant renvoyée par l'événement de création.
ng-dynamic-component est peut-être la meilleure et la plus simple des solutions, mais je ne l'ai pas encore testée.

La réponse de @Long Field est parfaite ! Voici un autre exemple (synchrone) :

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

En direct de http://plnkr.co/edit/fdP9Oc .

3 votes

Je dirais, que c'est un exemple de comment écrire le moins de code possible à faire la même chose que dans ma réponse stackoverflow.com/a/38888009/1679310 . Au cas où, qu'il devrait être utile-case (principalement le modèle RE-générateur) quand les conditions changent... le simple ngAfterViewInit appel avec un const template ne fonctionnera pas. Mais si votre tâche consistait à réduire l'approche décrite en détail ci-dessus (créer un modèle, créer un composant, créer un module, le compiler, créer une usine créer une instance) ... vous l'avez probablement fait

0 votes

Merci pour la solution : J'ai cependant des difficultés à charger le templateUrl et les styles, j'obtiens l'erreur suivante : Aucune implémentation de ResourceLoader n'a été fournie . Impossible de lire l'url localhost:3000/app/pages/pages_common.css Une idée de ce que j'ai manqué ?

0 votes

Serait-il possible de compiler le modèle html avec des données spécifiques pour les cellules de la grille comme le contrôle ? plnkr.co/edit/vJHUCnsJB7cwNJr2cCwp?p=preview Dans ce plunker, comment puis-je compiler le et montrer l'image dans la dernière colonne ? Vous pouvez m'aider ?

52voto

Ophir Stern Points 1009

Je dois être arrivé en retard à la fête, aucune des solutions proposées ici ne m'a semblé utile - elles étaient trop compliquées et ressemblaient trop à des solutions de fortune.

Ce que j'ai fini par faire, c'est utiliser Angular 4.0.0-beta.6 's ngComponentOutlet .

J'ai ainsi obtenu la solution la plus courte et la plus simple, entièrement écrite dans le fichier du composant dynamique.

  • Voici un exemple simple qui ne fait que recevoir du texte et le placer dans un modèle, mais vous pouvez évidemment le modifier en fonction de vos besoins :

    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;

    } }

  • Brève explication :

    1. my-component - le composant dans lequel un composant dynamique est rendu
    2. DynamicComponent - le composant à construire dynamiquement et il est rendu à l'intérieur de mon-composant

N'oubliez pas de mettre à jour toutes les bibliothèques angulaires vers ^Angular 4.0.0

J'espère que cela vous aidera, bonne chance !

UPDATE

Fonctionne également pour angular 5.

1 votes

Merci de m'avoir prévenu, je vais peut-être faire une mise à jour et tenter le coup - le faire dans Angular2 va forcément casser dans Angular4 de toute façon.

3 votes

Cela a très bien fonctionné pour moi avec Angular4. Le seul ajustement que j'ai dû faire était de pouvoir spécifier les modules d'importation pour le RuntimeComponentModule créé dynamiquement.

0 votes

Vous avez un plunkr qui fonctionne ou quelque chose comme ça ? Je n'arrive pas à le faire fonctionner avec Angular 4.0.0.

18voto

Steve Paul Points 135

J'ai décidé de comprimer tout ce que j'ai appris dans un seul fichier. . Il y a beaucoup à prendre ici, surtout par rapport à ce qui se passait avant la RC5. Notez que ce fichier source inclut le AppModule et le 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 {}`

10voto

Long Field Points 26

J'ai un exemple simple pour montrer comment faire un composant dynamique angular 2 rc6.

Supposons que vous ayez un modèle html dynamique = template1 et que vous souhaitiez le charger de manière dynamique, vous devez d'abord l'intégrer dans un composant

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

ici template1 comme html, peut contenir le composant ng2

A partir de rc6, il faut que @NgModule enveloppe ce composant. @NgModule, tout comme le module dans anglarJS 1, il découple différentes parties de l'application ng2, ainsi :

@Component({
  template: template1,

})
class DynamicComponent {

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

(Ici importez RouterModule car dans mon exemple il y a quelques composants de route dans mon html comme vous pouvez le voir plus tard)

Maintenant vous pouvez compiler DynamicModule comme : this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))

Et nous devons mettre ce qui précède dans app.moudule.ts pour le charger, veuillez voir mon app.moudle.ts. Pour plus de détails, consultez le site : https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts et app.moudle.ts

et voir la démo : http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview

3 votes

Donc, vous avez déclaré module1, module2, module3. Et si vous avez besoin d'un autre contenu de modèle "dynamique", vous devez créer une définition (fichier) de la forme moudle4 (module4.ts), n'est-ce pas ? Si oui, cela ne semble pas être dynamique. Il est statique, n'est-ce pas ? Ou est-ce que j'ai raté quelque chose ?

0 votes

Dans l'exemple ci-dessus, "template1" est une chaîne de caractères html, vous pouvez y mettre n'importe quoi et nous appelons cela un modèle dynamique, comme le demande cette question.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X