EDIT - relativo a 2.3.0 (2016-12-07)
NOTA: per ottenere la soluzione per la versione precedente, controlla la cronologia di questo post
Argomento simile è discusso qui Equivalente di $ compilare in Angular 2 . Dobbiamo usare JitCompiler
e NgModule
. Maggiori informazioni su NgModule
in Angular2 qui:
In breve
Esiste un plunker / esempio funzionante (modello dinamico, tipo di componente dinamico, modulo dinamico JitCompiler
, ... in azione)
L'entità è:
1) crea il modello
2) trova ComponentFactory
nella cache - vai a 7)
3) - crea Component
4) - crea Module
5) - compila Module
6) - restituisce (e cache per un uso successivo) ComponentFactory
7) usa Target e ComponentFactory
per creare un'istanza di dinamicoComponent
Ecco uno snippet di codice (altro qui ) - Il nostro generatore personalizzato sta restituendo appena creato / memorizzato nella cache ComponentFactory
e la vista Segnaposto di destinazione consuma per creare un'istanza delDynamicComponent
// 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;
//...
});
Questo è tutto - in poche parole. Per maggiori dettagli .. leggi sotto
.
TL & DR
Osserva un plunker e torna a leggere i dettagli nel caso in cui alcuni frammenti richiedano ulteriori spiegazioni
.
Spiegazione dettagliata - Angular2 RC6 ++ e componenti di runtime
Di seguito la descrizione di questo scenario , lo faremo
- creare un modulo
PartsModule:NgModule
(supporto di piccoli pezzi)
- creare un altro modulo
DynamicModule:NgModule
, che conterrà il nostro componente dinamico (e fare riferimento PartsModule
dinamicamente)
- creare un modello dinamico (approccio semplice)
- creare nuovo
Component
tipo (solo se il modello è cambiato)
- creare nuovo
RuntimeModule:NgModule
. Questo modulo conterrà il Component
tipo precedentemente creato
- chiamata
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
per ottenereComponentFactory
- creare un'istanza di
DynamicComponent
- lavoro del segnaposto Visualizza destinazione eComponentFactory
- assegnare
@Inputs
alla nuova istanza (interruttore da INPUT
a TEXTAREA
modifica) , consumano@Outputs
NgModule
Abbiamo bisogno di un NgModule
s.
Mentre vorrei mostrare un esempio molto semplice, in questo caso, avrei bisogno di tre moduli (in effetti 4 - ma non conto l'AppModule) . Per favore, prendi questo piuttosto che un semplice frammento come base per un generatore di componenti dinamici davvero solido.
Ci sarà un modulo per tutti i piccoli componenti, ad es string-editor
. text-editor
( date-editor
, number-editor
...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Dove DYNAMIC_DIRECTIVES
sono estensibili e destinati a contenere tutte le piccole parti utilizzate per il nostro modello / tipo di componente dinamico. Controlla app / parts / parts.module.ts
Il secondo sarà il modulo per la nostra gestione dinamica delle cose. Conterrà componenti di hosting e alcuni provider .. che saranno singoli. Pertanto li pubblicheremo in modo standard - conforRoot()
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
],
};
}
}
Controllare l'uso di forRoot()
inAppModule
Infine, avremo bisogno di un modulo di runtime ad hoc ... ma che verrà creato in seguito, come parte del DynamicTypeBuilder
lavoro.
Il quarto modulo, modulo applicativo, è quello che continua a dichiarare i provider di compilatori:
...
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
],
Leggi (leggi) molto di più su NgModule lì:
Un modello builder
Nel nostro esempio elaboreremo i dettagli di questo tipo di entità
entity = {
code: "ABC123",
description: "A description of this Entity"
};
Per creare un template
, in questo plunker usiamo questo builder semplice / ingenuo.
La vera soluzione, un vero generatore di modelli, è il luogo in cui l'applicazione può fare molto
// 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>";
}
}
Un trucco qui è: crea un modello che utilizza un insieme di proprietà note, ad es entity
. Tale proprietà (-ies) deve far parte del componente dinamico, che creeremo successivamente.
Per renderlo un po 'più semplice, possiamo usare un'interfaccia per definire le proprietà, che il nostro template builder può usare. Questo sarà implementato dal nostro tipo di componente dinamico.
export interface IHaveDynamicData {
public entity: any;
...
}
UN ComponentFactory
costruttore
La cosa molto importante qui è da tenere a mente:
il nostro tipo di componente, costruito con il nostro DynamicTypeBuilder
, potrebbe differire, ma solo dal suo modello (creato sopra) . Le proprietà dei componenti (ingressi, uscite o alcuni protetti) sono ancora le stesse. Se abbiamo bisogno di proprietà diverse, dovremmo definire diverse combinazioni di Template e Type Builder
Quindi, stiamo toccando il nucleo della nostra soluzione. The Builder, 1) creerà ComponentType
2) creerà la sua NgModule
3) compilazione ComponentFactory
4) memorizzerà nella cache per un successivo riutilizzo.
Una dipendenza che dobbiamo ricevere:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
Ed ecco uno snippet come ottenere 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);
});
});
}
Sopra creiamo e memorizziamo nella cache entrambi Component
e Module
. Perché se il modello (in effetti la vera parte dinamica di tutto questo) è lo stesso .. possiamo riutilizzarlo
E qui ci sono due metodi, che rappresentano il modo davvero fantastico di creare classi / tipi decorati in fase di esecuzione. Non solo @Component
ma anche il@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;
}
Importante:
i nostri tipi dinamici dei componenti differiscono, ma solo per modello. Quindi usiamo questo fatto per memorizzarli nella cache . Questo è davvero molto importante. Anche Angular2 li memorizzerà nella cache in base al tipo . E se dovessimo ricreare per lo stesso modello stringhe di nuovi tipi ... inizieremo a generare perdite di memoria.
ComponentFactory
utilizzato dal componente di hosting
Il pezzo finale è un componente che ospita l'obiettivo del nostro componente dinamico, ad es <div #dynamicContentPlaceHolder></div>
. Otteniamo un riferimento ad esso e utilizziamo ComponentFactory
per creare un componente. Questo è in breve, e qui ci sono tutti i pezzi di quel componente (se necessario, apri il plunker qui )
Riassumiamo innanzitutto le dichiarazioni di importazione:
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
) {}
...
Riceviamo solo costruttori di modelli e componenti. Successivamente ci sono le proprietà che sono necessarie per il nostro esempio (più nei commenti)
// 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"
};
In questo semplice scenario, il nostro componente di hosting non ne ha @Input
. Quindi non deve reagire ai cambiamenti. Ma nonostante ciò (e per essere pronti per i prossimi cambiamenti) - dobbiamo introdurre alcuni flag se il componente era già (in primo luogo) avviato. E solo allora possiamo iniziare la magia.
Infine useremo il nostro componente builder, ed è appena compilato / memorizzato nella cache ComponentFacotry
. Il nostro segnaposto di destinazione verrà chiesto di istanziare laComponent
con quella fabbrica.
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;
//...
});
}
piccola estensione
Inoltre, dobbiamo mantenere un riferimento al modello compilato ... per poterlo correttamente destroy()
, ogni volta che lo cambieremo.
// 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;
}
}
fatto
È praticamente tutto. Non dimenticare di distruggere tutto ciò che è stato costruito in modo dinamico (ngOnDestroy) . Inoltre, assicurati di memorizzare nella cache dinamica types
e modules
se l'unica differenza è il loro modello.
Controlla tutto in azione qui
per vedere le versioni precedenti (es. relative a RC5) di questo post, controlla la cronologia