EDIT - relacionado a 2.3.0 (07/12/2016)
NOTA: para obter solução para a versão anterior, verifique o histórico desta postagem
Tópico semelhante é discutido aqui Equivalente a $ compile no Angular 2 . Precisamos usar JitCompilere NgModule. Leia mais sobre NgModuleno Angular2 aqui:
Em poucas palavras
Há um exemplo / exemplo de trabalho ativo (modelo dinâmico, tipo de componente dinâmico, módulo dinâmico JitCompiler, ... em ação)
O principal é:
1) criar o modelo
2) encontrar ComponentFactoryno cache - vá para 7)
3) - criar Component
4) - criar Module
5) - compilar Module
6) - retornar (e armazenar em cache para uso posterior) ComponentFactory
7) usar o Target e ComponentFactorycriar uma instância de dinâmicoComponent
Aqui está um trecho de código (mais sobre isso aqui ) - Nosso Construtor personalizado está retornando apenas construído / armazenado em cache ComponentFactorye a exibição Espaço reservado de destino consome para criar uma instância doDynamicComponent
// 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;
//...
});
É isso - em poucas palavras. Para obter mais detalhes .. leia abaixo
.
TL&DR
Observe um desentupidor e volte para ler os detalhes, caso algum trecho exija mais explicações
.
Explicação detalhada - Angular2 RC6 ++ e componentes de tempo de execução
Abaixo da descrição desse cenário , iremos
- criar um módulo
PartsModule:NgModule (suporte de pequenos pedaços)
- crie outro módulo
DynamicModule:NgModule, que conterá nosso componente dinâmico (e fará referência PartsModuledinamicamente)
- criar modelo dinâmico (abordagem simples)
- crie um novo
Componenttipo (somente se o modelo tiver sido alterado)
- criar novo
RuntimeModule:NgModule. Este módulo conterá o Componenttipo criado anteriormente
- ligue
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)para obterComponentFactory
- crie uma Instância do
DynamicComponenttrabalho - do espaço reservado Exibir Destino eComponentFactory
- atribuir
@Inputsa nova instância (alternar de INPUTpara TEXTAREAedição) , consumir@Outputs
NgModule
Nós precisamos de um NgModules.
Embora eu queira mostrar um exemplo muito simples, neste caso, precisaria de três módulos (na verdade 4 - mas não conto o AppModule) . Por favor, tome isso em vez de um trecho simples como base para um gerador de componentes dinâmicos realmente sólido.
Haverá um módulo para todos os componentes pequenos, por exemplo string-editor, text-editor ( date-editor, number-editor...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Onde DYNAMIC_DIRECTIVESsão extensíveis e destinam-se a conter todas as peças pequenas usadas para nosso modelo / modelo dinâmico de componente. Verifique app / parts / parts.module.ts
O segundo será o módulo para o nosso manuseio de material dinâmico. Ele conterá componentes de hospedagem e alguns provedores .. que serão singletons. Para isso, publicaremos de maneira padrão - comforRoot()
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
],
};
}
}
Verifique o uso do forRoot()noAppModule
Por fim, precisaremos de um módulo adhoc, de tempo de execução ... mas isso será criado posteriormente, como parte do DynamicTypeBuildertrabalho.
O quarto módulo, módulo de aplicativo, é aquele que mantém declara os provedores de compilador:
...
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
],
Leia (leia) muito mais sobre o NgModule :
Um construtor de modelos
Em nosso exemplo, processaremos detalhes desse tipo de entidade
entity = {
code: "ABC123",
description: "A description of this Entity"
};
Para criar um template, neste plunker usamos este construtor simples / ingênuo.
A solução real, um construtor de modelos real, é o local em que seu aplicativo pode fazer muito
// 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>";
}
}
Um truque aqui é - ele cria um modelo que usa algum conjunto de propriedades conhecidas, por exemplo entity. Essas propriedades devem ser parte do componente dinâmico, que criaremos a seguir.
Para facilitar um pouco, podemos usar uma interface para definir propriedades que nosso construtor de modelos pode usar. Isso será implementado pelo nosso tipo de componente dinâmico.
export interface IHaveDynamicData {
public entity: any;
...
}
Um ComponentFactoryconstrutor
O mais importante aqui é ter em mente:
nosso tipo de componente, construído com o nosso DynamicTypeBuilder, pode diferir - mas apenas pelo modelo (criado acima) . As propriedades dos componentes (entradas, saídas ou algumas protegidas) ainda são as mesmas. Se precisarmos de propriedades diferentes, definiremos combinações diferentes de Construtor de modelos e tipos
Então, estamos tocando o núcleo da nossa solução. O Construtor 1) criará ComponentType2) criará NgModule3) compilará ComponentFactory4) armazenará em cache para reutilização posterior.
Uma dependência que precisamos receber:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from '@angular/compiler';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
E aqui está um trecho de como obter 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);
});
});
}
Acima, criamos e armazenamos em cache ambos Componente Module. Porque se o modelo (na verdade, a parte dinâmica real de tudo isso) é o mesmo ... podemos reutilizar
E aqui estão dois métodos, que representam a maneira realmente legal de criar classes / tipos decorados em tempo de execução. Não apenas @Componentmas também o@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:
nossos tipos dinâmicos de componentes diferem, mas apenas por modelo. Então, usamos esse fato para armazená- los em cache . Isso é realmente muito importante. Angular2 também armazenará em cache esses ... pelo tipo . E se recriarmos para o mesmo modelo novas seqüências de caracteres ... começaremos a gerar vazamentos de memória.
ComponentFactory usado hospedando o componente
A peça final é um componente que hospeda o destino do nosso componente dinâmico, por exemplo <div #dynamicContentPlaceHolder></div>. Nós obtemos uma referência a ele e usamos ComponentFactorypara criar um componente. Em poucas palavras, e aqui estão todas as peças desse componente (se necessário, abra o êmbolo aqui )
Vamos primeiro resumir as instruções de importação:
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
) {}
...
Acabamos de receber construtores de modelos e componentes. A seguir estão as propriedades necessárias para o nosso exemplo (mais nos comentários)
// 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"
};
Nesse cenário simples, nosso componente de hospedagem não possui nenhum @Input. Portanto, não precisa reagir às mudanças. Mas, apesar desse fato (e estar pronto para as próximas mudanças) - precisamos introduzir algum sinalizador se o componente já foi (em primeiro lugar) iniciado. E só então podemos começar a mágica.
Finalmente, usaremos nosso construtor de componentes, e ele é apenas compilado / armazenado em cache ComponentFacotry . Nosso espaço reservado alvo será solicitado para instanciar oComponent com essa fábrica.
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;
//...
});
}
pequena extensão
Além disso, precisamos manter uma referência ao modelo compilado ... para podermos usá- destroy()lo adequadamente , sempre que o alterarmos.
// 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;
}
}
feito
É isso mesmo. Não se esqueça de Destruir qualquer coisa que foi construída dinamicamente (ngOnDestroy) . Além disso, certifique-se de armazenar em cache dinâmico typese modulesse a única diferença é o modelo deles.
Confira tudo em ação aqui
para ver versões anteriores (por exemplo, relacionadas ao RC5) desta publicação, verifique o histórico