Je veux dynamiquement créer un modèle . Cela devrait être utilisé pour construire un ComponentType
à Runtime et le placer (même le remplacer) quelque part à l'intérieur de le composant d'hébergement.
Jusqu'au RC4, j'utilisais ComponentResolver
, mais avec le RC5, je reçois le message suivant:
ComponentResolver
est obsolète pour la compilation dynamique. Utilisez plutôtComponentFactoryResolver
avec le fournisseur@NgModule/@Component.entryComponents
ou ANALYZE_FOR_ENTRY_COMPONENTS. Pour la compilation à l'exécution uniquement , vous pouvez également utiliserCompiler.compileComponentSync/Async
.
J'ai trouvé ce document (offical angular2)
Et comprenez que je peux utiliser soit
ngIf
avec ComponentFactoryResolver
. Si je transmettrai des composants connus à l'hébergement d'un élément de @Component({entryComponents: [comp1, comp2], ...})
- je peux utiliser .resolveComponentFactory(componentToRender);
Compiler
...Mais la question est de savoir comment utiliser cela Compiler
? La note ci-dessus indique que je devrais 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/date/référence différent editors
selon les types de propriétés, certaines propriétés ont été ignorées pour certains utilisateurs ...) . C'est à dire. Ceci est un exemple, une 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 transmettre les modèles existants ... J'ai besoin d'une solution avec le Compiler
Souhaitez-vous utiliser ces fonctionnalités avec AOT (compilation anticipée)? Obtenez-vous:
Erreur: Une erreur s'est produite lors de la résolution statique des valeurs de symbole. Les appels de fonction ne sont pas pris en charge. Envisagez de remplacer la fonction ou lambda par une référence à une fonction exportée (position 65:17 dans le fichier .ts original), en résolvant le symbole COMPILER_PROVIDERS dans .../node_modules/@angular/compiler/src/compiler.d.ts,
S'il vous plaît, laissez votre commentaire, votez ici:
REMARQUE: pour obtenir la solution de la version précédente, consultez l'historique de ce message
Un sujet similaire est traité ici équivalent de $ compile in Angular 2 . Nous devons utiliser JitCompiler
et NgModule
. En savoir plus surNgModule
in Angular2 ici:
Il y a n plunker/exemple de travail (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éez Component
4) - Créez Module
5) - Compilez Module
6) - renvoie (et cache pour une utilisation ultérieure) ComponentFactory
7) utiliser Target et ComponentFactory
pour créer une instance de dynamique Component
Voici un extrait de code (plusieurs - ici ) - Notre générateur personnalisé renvoie le fichier nouvellement construit/mis en cache ComponentFactory
et la vue que l’espace réservé cible consomme pour créer une instance de 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 ça - en bref, ça. Pour obtenir plus de détails .. lire ci-dessous
.
Observez un plunker et revenez pour lire les détails au cas où un extrait demanderait plus d'explications
.
Ci-dessous la description de ce scénario , nous allons
PartsModule:NgModule
(détenteur de petites pièces) DynamicModule:NgModule
, qui contiendra notre composant dynamique (et référencé PartsModule
dynamiquement) Component
type (uniquement si le modèle a été modifié) RuntimeModule:NgModule
. Ce module contiendra le type Component
précédemment crééJitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
pour obtenir ComponentFactory
DynamicComponent
- de l'espace réservé Vue cible et de ComponentFactory
@Inputs
à nouvelle instance (bascule de INPUT
à TEXTAREA
édition) , consomme @Outputs
Nous avons besoin d'un NgModule
s.
Bien que j'aimerais montrer un exemple très simple, dans ce cas, il me faudrait trois modules (en fait 4 - mais je ne compte pas le module d'application) . S'il vous plaît, prenez ceci plutôt qu'un simple extrait comme base pour un générateur de composants dynamiques vraiment solide.
Il y aura un module pour tous les petits composants, par exemple. string-editor
, text-editor
(date-editor
, number-editor
...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Où
DYNAMIC_DIRECTIVES
sont extensibles et destinés à contenir toutes les petites pièces utilisées pour notre modèle/type de composant dynamique. Vérifiez app/parts/parts.module.ts
Le second sera un module pour notre traitement dynamique des commandes. Il contiendra des composants d'hébergement et quelques fournisseurs .. qui seront des singletons. Pour cela 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
forRoot()
dans leAppModule
Enfin, nous aurons besoin d’un module d’exécution, ad hoc, mais qui sera créé ultérieurement, dans le cadre du travail DynamicTypeBuilder
.
Le quatrième module, le module d'application, est celui qui conserve les fournisseurs de compilateur déclarés:
...
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
],
Lisez (lisez) beaucoup plus à propos de NgModule il:
Dans notre exemple, nous traiterons les détails de ce type d’entité entité
entity = {
code: "ABC123",
description: "A description of this Entity"
};
Pour créer un template
, dans ce plunker , nous utilisons ce générateur simple/naïf.
La vraie solution, un vrai constructeur de templates, est l'endroit où votre application peut 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>";
}
}
Une astuce consiste à créer un modèle qui utilise un ensemble de propriétés connues, par exemple. entity
. Cette propriété (-ies) doit faire partie du composant dynamique, que nous allons créer ensuite.
Pour simplifier un peu les choses, nous pouvons utiliser une interface pour définir les propriétés que notre générateur de modèles peut utiliser. Ceci sera implémenté par notre type de composant dynamique.
export interface IHaveDynamicData {
public entity: any;
...
}
ComponentFactory
La chose la plus importante ici est de garder à l'esprit:
notre type de composant, construit avec notre
DynamicTypeBuilder
, peut différer - mais uniquement par son modèle (créé ci-dessus) . Les propriétés des composants (entrées, sorties ou certains protégés) sont toujours les mêmes. Si nous avons besoin de propriétés différentes, nous devrions définir une combinaison différente de Template et Type Builder
Nous touchons donc le cœur de notre solution. Le constructeur va 1) créer ComponentType
2) créer son NgModule
3) compiler ComponentFactory
4) le mettre en cache pour le réutiliser ultérieurement.
Une dépendance à 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 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);
});
});
}
Ci-dessus, nous créons et mettons en cache les deux
Component
etModule
. Parce que si le template (en fait, la partie dynamique réelle de tout cela) est le même .. nous pouvons le réutiliser
Et voici deux méthodes, qui représentent la manière vraiment cool de créer un déjà décoré classes/types en runtime. 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:
nos types de composants dynamiques diffèrent, mais juste par modèle. Nous utilisons donc ce fait pour les mettre en cache . C'est vraiment très important. Angular2 mettra également en cache ces .. par le type . Et si nous recréions pour le même modèle de chaînes de modèles de nouveaux types ... nous commencerions à générer des fuites de mémoire.
ComponentFactory
utilisé par le composant d'hébergementLe dernier élément est un composant qui héberge la cible de notre composant dynamique, par exemple. <div #dynamicContentPlaceHolder></div>
. Nous y obtenons une référence et utilisons ComponentFactory
pour créer un composant. En résumé, voici toutes les pièces de ce composant (si nécessaire, ouvrez plunker here )
Résumons d’abord les instructions 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 venons de recevoir des générateurs de modèles et de composants. Viennent ensuite les propriétés nécessaires à 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 n'a pas de @Input
. Donc, il ne doit pas réagir aux changements. Mais malgré ce fait (et pour être prêt pour les changements à venir) - nous devons introduire un indicateur si le composant était déjà (premièrement) initié. Et alors seulement nous pourrons commencer la magie.
Enfin, nous allons utiliser notre générateur de composants, et son vient d'être compilé/mis en cache ComponentFacotry
. Notre espace réservé cible sera invité à instancier Component
avec cette fabrique.
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;
//...
});
}
De plus, nous devons garder une référence au modèle compilé. Pour pouvoir le destroy()
le bien, à chaque fois que 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;
}
}
C'est à peu près ça. N'oubliez pas de détruire tout ce qui a été construit dynamiquement (ngOnDestroy) . Assurez-vous également de mettre en cache les types
et modules
dynamiques si la seule différence est leur modèle.
Vérifiez tout en action ici
pour voir les versions précédentes (exemple: lié à RC5) de ce post, consultez le historique
EDIT (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 clic. et l'a testé avec Angular 4.3.
Pour Angular4, ngComponentOutlet tel que décrit dans réponse d'Ophir est une bien meilleure solution. Mais pour le moment ne supporte pas les entrées et sorties encore. Si [ce PR] ( https://github.com/angular/angular/pull/15362] est accepté, cela serait possible via l'instance de composant renvoyée par l'événement create.
ng-dynamic-composant est peut-être la solution la plus simple et la meilleure, 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 {}
Live at http://plnkr.co/edit/fdP9Oc .
Je devais être arrivé à la soirée tardivement, aucune des solutions ici ne me paraissait utile - trop en désordre et semblait trop complexe.
Ce que j'ai fini par faire, c'est d'utiliser Angular 4.0.0-beta.6
's ngComponentOutlet .
Cela m'a donné la solution la plus courte et la plus simple, inscrite dans le fichier du composant dynamique.
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;
}
}
my-component
- le composant dans lequel un composant dynamique est renduDynamicComponent
- le composant à construire de manière dynamique et restitué dans my-composantN'oubliez pas de mettre à jour toutes les bibliothèques angulaires vers ^ Angular 4.0.0
En espérant que ça aide, bonne chance!
UPDATE
Fonctionne également pour angular 5.
J'ai décidé de compacter tout ce que j'ai appris dans un seul fichier. Il y a beaucoup à prendre ici surtout par rapport à avant RC5. Notez que ce fichier source inclut AppModule et 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 {}`
J'ai un exemple simple pour montrer comment faire un composant dynamique angulaire 2 rc6.
Disons que vous avez un template html dynamique = template1 et que vous souhaitez un chargement dynamique, tout d’abord intégré dans le composant
@Component({template: template1})
class DynamicComponent {}
ici template1 en html, peut contenir un composant ng2
De rc6, vous devez avoir @NgModule pour envelopper ce composant. @NgModule, tout comme le module d'anglarJS 1, il découple différentes parties de l'application ng2, donc:
@Component({
template: template1,
})
class DynamicComponent {
}
@NgModule({
imports: [BrowserModule,RouterModule],
declarations: [DynamicComponent]
})
class DynamicModule { }
(Ici, importez RouterModule comme dans mon exemple, il y a quelques composants de route dans mon HTML comme vous pourrez le voir plus tard)
Vous pouvez maintenant compiler DynamicModule en tant que: this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))
Et nous avons besoin de mettre ci-dessus dans app.moudule.ts pour le charger, s'il vous plaît voir mon app.moudle.ts. Pour plus d'informations, consultez: 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
Réponse de juin 2019
Bonne nouvelle! Il semble que le package @ angular/cdk dispose désormais d'un support de première classe pour portails !
Au moment de la rédaction de ce document, je n'ai pas trouvé les documents officiels ci-dessus particulièrement utiles (notamment en ce qui concerne l'envoi de données et la réception d'événements à partir des composants dynamiques). En résumé, vous devrez:
Étape 1) Mettez à jour votre AppModule
Importez PortalModule
à partir du package cdk
et enregistrez votre ou vos composants dynamiques à l'intérieur de entryComponents
@NgModule({
declarations: [ ..., AppComponent, MyDynamicComponent, ... ]
imports: [ ..., PortalModule, ... ],
entryComponents: [ ..., MyDynamicComponent, ... ]
})
export class AppModule { }
Étape 2. Option A: Si vous n'avez PAS besoin de transmettre des données à et de recevoir des événements de vos composants dynamiques :
@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{
}
Étape 2. Option B: Si vous DEVEZ transmettre des données et recevoir des événements de vos composants dynamiques :
// 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 = '';
}
Résolu ceci dans Angular 2 Version finale simplement en utilisant la directive dynamicComponent de ng-dynamic .
Usage:
<div *dynamicComponent="template; context: {text: text};"></div>
Où template est votre modèle dynamique et le contexte peut être défini sur tout modèle de données dynamique auquel vous souhaitez associer votre modèle.
Pour faire suite à l'excellente réponse de Radmin, un petit Tweak est nécessaire pour tous ceux qui utilisent angular-cli version 1.0.0-beta.22 ou supérieure.
COMPILER_PROVIDERS
ne peut plus être importé (pour plus de détails, voir angular-cli GitHub ).
La solution de contournement consiste donc à ne pas utiliser COMPILER_PROVIDERS
et JitCompiler
dans la section providers
, mais à utiliser plutôt JitCompilerFactory
à partir de '@ angular/compiler' dans la classe de constructeur de types:
private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();
Comme vous pouvez le constater, il n’est pas injectable et n’a donc aucune dépendance avec l’ID. Cette solution devrait également fonctionner pour des projets n'utilisant pas angular-cli.
Je souhaite ajouter quelques détails en plus de cet excellent article de Radim.
J'ai pris cette solution, y ai travaillé un peu et j'ai rapidement rencontré quelques limitations. Je vais simplement les décrire, puis donner la solution à cela également.
J'ai créé une autre question basée sur ce post, sur la manière d'atteindre ces limitations, que vous pouvez trouver ici:
compilation de modèles dynamiques récursifs dans angular2
Je vais juste exposer les réponses à ces limitations, si vous rencontrez le même problème que moi, car cela rend la solution plus flexible. Il serait génial de mettre à jour le premier plunker avec cela également.
Pour permettre l'imbrication de détails dynamiques imbriqués l'un dans l'autre, vous devez ajouter DynamicModule.forRoot () dans l'instruction d'importation du type type.builder.ts .
protected createComponentModule (componentType: any) {
@NgModule({
imports: [
PartsModule,
DynamicModule.forRoot() //this line here
],
declarations: [
componentType
],
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
De plus, il n’était pas possible d’utiliser <dynamic-detail>
dans l’une des parties sous forme d’éditeur de chaîne ou de texte.
Pour l'activer, vous devrez changer parts.module.ts
et dynamic.module.ts
Dans parts.module.ts
Vous devrez ajouter DynamicDetail
dans le DYNAMIC_DIRECTIVES
export const DYNAMIC_DIRECTIVES = [
forwardRef(() => StringEditor),
forwardRef(() => TextEditor),
DynamicDetail
];
De plus, dans le dynamic.module.ts
, vous devrez supprimer dynamicDetail car ils font maintenant partie des pièces.
@NgModule({
imports: [ PartsModule ],
exports: [ PartsModule],
})
Un plunker modifié peut être trouvé ici: http://plnkr.co/edit/UYnQHF?p=preview (Je n'ai pas résolu ce problème, je ne suis que le messager :-D)
Enfin, il n’a pas été possible d’utiliser des modèles dans les pièces créées sur les composants dynamiques. Une solution (ou une solution de contournement. Je ne sais pas s'il s'agit d'un bogue angulaire ou d'une mauvaise utilisation du framework) était de créer un compilateur dans le constructeur au lieu de l'injecter.
private _compiler;
constructor(protected compiler: RuntimeCompiler) {
const compilerFactory : CompilerFactory =
platformBrowserDynamic().injector.get(CompilerFactory);
this._compiler = compilerFactory.createCompiler([]);
}
Ensuite, utilisez le _compiler
pour compiler, puis templateUrls est également activé.
return new Promise((resolve) => {
this._compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories) =>
{
let _ = window["_"];
factory = _.find(moduleWithFactories.componentFactories, { componentType: type });
this._cacheOfFactories[template] = factory;
resolve(factory);
});
});
J'espère que ceci aide quelqu'un d'autre!
Cordialement Morten
Dans angular 7.x, j'ai utilisé des éléments angulaires pour cela.
Installer @ angular-elements npm i @ angular/elements -s
Créer un service accessoire.
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;
}
}
Notez que votre balise d'élément personnalisé doit être différente avec le sélecteur de composant angulaire. dans AppUserIconComponent:
...
selector: app-user-icon
...
et dans ce cas, le nom de la balise personnalisée j'ai utilisé "user-icon".
@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(
dynamicComponents: DynamicComponentsService,
) {
dynamicComponents.register();
}
}
dynamicComponents.create('user-icon', {user:{...}});
ou comme ceci:
const html = `<div class="wrapper"><user-icon class="user-icon" user='${JSON.stringify(rec.user)}'></user-icon></div>`;
this.content = this.domSanitizer.bypassSecurityTrustHtml(html);
(dans le modèle):
<div class="comment-item d-flex" [innerHTML]="content"></div>
Notez que dans le second cas, vous devez transmettre des objets avec JSON.stringify, puis les analyser à nouveau. Je ne peux pas trouver de meilleure solution.
J'essaie moi-même de voir comment mettre à jour RC4 vers RC5. Je suis donc tombé par hasard sur cette entrée. Une nouvelle approche de la création de composants dynamiques reste encore un peu mystérieuse. Je ne suggère donc rien sur le résolveur d'usine de composants.
Mais ce que je peux suggérer, c’est une approche un peu plus claire de la création de composants sur ce scénario. Il suffit d’utiliser un commutateur dans un modèle qui créerait un éditeur de chaîne ou un éditeur de texte en fonction de certaines conditions, comme ceci:
<form [ngSwitch]="useTextarea">
<string-editor *ngSwitchCase="false" propertyName="'code'"
[entity]="entity"></string-editor>
<text-editor *ngSwitchCase="true" propertyName="'code'"
[entity]="entity"></text-editor>
</form>
Et au fait, "[" dans [prop] expression a une signification, cela indique une liaison de données, vous pouvez donc et même les omettre si vous savez que vous n'avez pas besoin de lier une propriété à une variable.
Voici l'exemple des contrôles de formulaire dynamiques générés à partir du serveur.
https://stackblitz.com/edit/angular-t3mmg6
Cet exemple est dynamique Les contrôles de formulaire sont dans le composant add (c'est ici que vous pouvez obtenir les contrôles de formulaire du serveur). Si vous voyez la méthode addcomponent, vous pouvez voir les contrôles de formulaire. Dans cet exemple, je n'utilise pas de matériau angulaire, mais cela fonctionne (j'utilise @ work). Ceci est ciblé sur 6 angulaire, mais fonctionne dans toutes les versions précédentes.
Besoin d'ajouter JITComplierFactory pour AngularVersion 5 et versions supérieures.
Merci
Vijay
Pour ce cas particulier, utiliser une directive pour créer dynamiquement le composant serait une meilleure option. Exemple:
Dans le HTML où vous voulez créer le composant
<ng-container dynamicComponentDirective [someConfig]="someConfig"></ng-container>
Je voudrais aborder et concevoir la directive de la manière suivante.
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;
}
}
Ainsi, dans vos composants, le texte, la chaîne, la date, peu importe, quelle que soit la configuration que vous avez transmise dans le code HTML dans l'élément ng-container
serait disponible.
La configuration, yourConfig
name__, peut être identique et définir vos métadonnées.
En fonction de votre configuration ou de votre type d’entrée, la directive doit agir en conséquence et, à partir des types pris en charge, elle rendra le composant approprié. Sinon, cela enregistrera une erreur.