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
- créer un module
PartsModule:NgModule
(support de petits morceaux)
- créer un autre module
DynamicModule:NgModule
qui contiendra notre composant dynamique (et la référence PartsModule
dynamiquement)
- créer un modèle dynamique (approche simple)
- créer un nouveau
Component
type (seulement si le modèle a changé)
- créer un nouveau
RuntimeModule:NgModule
. Ce module contiendra les Component
type
- appelez
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
pour obtenir ComponentFactory
- créer une instance de la
DynamicComponent
- de l'espace réservé à la cible de vue et ComponentFactory
- 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
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. Sigh0 votes
@AugieGardner Il y a une raison pour laquelle cela n'est pas possible par conception. Angular n'est pas responsable des mauvaises décisions architecturales ou des systèmes hérités que certaines personnes ont. Si vous voulez analyser le code HTML existant, vous êtes libre d'utiliser un autre framework, car Angular fonctionne parfaitement bien avec les composants Web. Il est plus important de fixer des limites claires pour guider les hordes de programmeurs inexpérimentés que d'autoriser des bidouillages malhonnêtes pour quelques systèmes hérités.