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 JitCompiler
e NgModule
. Leia mais sobre NgModule
no 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 ComponentFactory
no 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 ComponentFactory
criar 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 ComponentFactory
e 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 PartsModule
dinamicamente)
- criar modelo dinâmico (abordagem simples)
- crie um novo
Component
tipo (somente se o modelo tiver sido alterado)
- criar novo
RuntimeModule:NgModule
. Este módulo conterá o Component
tipo criado anteriormente
- ligue
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
para obterComponentFactory
- crie uma Instância do
DynamicComponent
trabalho - do espaço reservado Exibir Destino eComponentFactory
- atribuir
@Inputs
a nova instância (alternar de INPUT
para TEXTAREA
edição) , consumir@Outputs
NgModule
Nós precisamos de um NgModule
s.
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_DIRECTIVES
sã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 DynamicTypeBuilder
trabalho.
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 ComponentFactory
construtor
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á ComponentType
2) criará NgModule
3) compilará ComponentFactory
4) 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 Component
e 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 @Component
mas 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 ComponentFactory
para 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 types
e modules
se 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