test - typings.d.ts angular 5
¿Cómo puedo usar/crear plantillas dinámicas para compilar componentes dinámicos con Angular 2.0? (14)
EDITAR - relacionado con 2.3.0 (2016-12-07)
NOTA: para obtener una solución para la versión anterior, consulte el historial de esta publicación
Un tema similar se discute aquí
Equivalente de $ compilar en Angular 2
.
Necesitamos usar
JitCompiler
y
NgModule
.
Lea más sobre
NgModule
en Angular2 aquí:
En una palabra
Hay
un plunker / ejemplo que funciona
(plantilla dinámica, tipo de componente dinámico, módulo dinámico,
JitCompiler
, ... en acción)
El principal es:
1)
crear plantilla
2)
encuentre
ComponentFactory
en caché -
vaya a
7)
3) - crear
Component
4) - crear
Module
5) -
Module
compilación
6) - retorno (y caché para uso posterior)
ComponentFactory
7)
use
Target
y
ComponentFactory
para crear una instancia de
Component
dinámico
Aquí hay un fragmento de código
(más de esto
aquí
)
: nuestro generador personalizado está devolviendo
ComponentFactory
recién construido / almacenado en caché y la vista El marcador de posición de destino consume para crear una instancia 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;
//...
});
Esto es todo, en pocas palabras. Para obtener más detalles ... lea a continuación
.
TL&DR
Observe un saqueador y vuelva a leer los detalles en caso de que algún fragmento requiera más explicación.
.
Explicación detallada - Angular2 RC6 ++ y componentes de tiempo de ejecución
Debajo de la descripción de este escenario , lo haremos
-
crear un módulo
PartsModule:NgModule
(soporte de piezas pequeñas) -
cree otro módulo
DynamicModule:NgModule
, que contendrá nuestro componente dinámico (yPartsModule
referencia aPartsModule
dinámicamente) - crear plantilla dinámica (enfoque simple)
-
crear un nuevo tipo de
Component
(solo si la plantilla ha cambiado) -
crear nuevo
RuntimeModule:NgModule
. Este módulo contendrá el tipo deComponent
creado previamente -
llame a
JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
para obtenerComponentFactory
-
crear una instancia de
DynamicComponent
: trabajo del marcador de posición Ver destino yComponentFactory
-
Asignar
@Inputs
a una nueva instancia (cambiar deINPUT
aTEXTAREA
edición) , consumir@Outputs
NgModule
Necesitamos un
NgModule
s.
Si bien me gustaría mostrar un ejemplo muy simple, en este caso, necesitaría tres módulos (de hecho 4, pero no cuento el AppModule) . Por favor, tome esto en lugar de un simple fragmento como base para un generador de componentes dinámicos realmente sólido.
Habrá
un
módulo para todos los componentes pequeños, por ejemplo
string-editor
text-editor
(
text-editor
date-editor
number-editor
...)
@NgModule({
imports: [
CommonModule,
FormsModule
],
declarations: [
DYNAMIC_DIRECTIVES
],
exports: [
DYNAMIC_DIRECTIVES,
CommonModule,
FormsModule
]
})
export class PartsModule { }
Donde
DYNAMIC_DIRECTIVES
son extensibles y están destinados a contener todas las piezas pequeñas utilizadas para nuestra plantilla / tipo de Componente dinámico. Verifique app / parts / parts.module.ts
El segundo será el módulo para nuestro manejo dinámico de cosas.
Contendrá componentes de hosting y algunos proveedores ... que serán singletons.
Por lo tanto, los publicaremos de manera estándar, con
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
],
};
}
}
Verifique el uso de
forRoot()
en elAppModule
Finalmente, necesitaremos un módulo adhoc, runtime ... pero que se creará más adelante, como parte del trabajo
DynamicTypeBuilder
.
El cuarto módulo, módulo de aplicación, es el que mantiene declara proveedores de compiladores:
...
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
],
Lea (lea) mucho más sobre NgModule allí:
Un creador de plantillas
En nuestro ejemplo procesaremos detalles de este tipo de entidad.
entity = {
code: "ABC123",
description: "A description of this Entity"
};
Para crear una
template
, en este
plunker
usamos este constructor simple / ingenuo.
La solución real, un generador de plantillas real, es el lugar donde su aplicación puede hacer mucho
// 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 truco aquí es: construye una plantilla que utiliza algún conjunto de propiedades conocidas, por ejemplo,
entity
.
Dichas propiedades deben ser parte del componente dinámico, que crearemos a continuación.
Para hacerlo un poco más fácil, podemos usar una interfaz para definir propiedades, que nuestro generador de plantillas puede usar. Esto será implementado por nuestro tipo de componente dinámico.
export interface IHaveDynamicData {
public entity: any;
...
}
Un constructor
ComponentFactory
Lo muy importante aquí es tener en cuenta:
nuestro tipo de componente,
DynamicTypeBuilder
con nuestroDynamicTypeBuilder
, podría diferir, pero solo por su plantilla (creada anteriormente) . Las propiedades de los componentes (entradas, salidas o algunas protegidas) siguen siendo las mismas. Si necesitamos diferentes propiedades, deberíamos definir diferentes combinaciones de Template y Type Builder
Entonces, estamos tocando el núcleo de nuestra solución.
El
NgModule
1) creará
ComponentType
2) creará su
NgModule
3) compilará
ComponentFactory
4) lo
almacenará
en
caché
para su posterior reutilización.
Una dependencia que necesitamos recibir:
// plunker - app/dynamic/type.builder.ts
import { JitCompiler } from ''@angular/compiler'';
@Injectable()
export class DynamicTypeBuilder {
// wee need Dynamic component builder
constructor(
protected compiler: JitCompiler
) {}
Y aquí hay un fragmento de cómo obtener una
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);
});
});
}
Arriba creamos y almacenamos en caché
Component
yModule
. Porque si la plantilla (de hecho, la parte dinámica real de todo eso) es la misma ... podemos reutilizar
Y aquí hay dos métodos, que representan la forma realmente genial de cómo crear clases / tipos
decorados
en tiempo de ejecución.
No solo
@Component
sino también
@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:
Nuestros tipos dinámicos de componentes difieren, pero solo por plantilla. Entonces usamos ese hecho para almacenarlos en caché . Esto es realmente muy importante. Angular2 también los almacenará en caché por tipo . Y si recreáramos para la misma plantilla cadenas nuevos tipos ... comenzaremos a generar pérdidas de memoria.
ComponentFactory
utilizada por el componente de hosting
La pieza final es un componente, que aloja el objetivo de nuestro componente dinámico, por ejemplo,
<div #dynamicContentPlaceHolder></div>
.
Obtenemos una referencia y usamos
ComponentFactory
para crear un componente.
En pocas palabras, y aquí están todas las piezas de ese componente
(si es necesario, abra el
plunker aquí
)
Primero resumamos las declaraciones de importación:
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 recibir, constructores de plantillas y componentes. Luego están las propiedades que se necesitan para nuestro ejemplo (más en comentarios)
// 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"
};
En este escenario simple, nuestro componente de alojamiento no tiene ningún
@Input
.
Por lo tanto, no tiene que reaccionar a los cambios.
Pero a pesar de ese hecho
(y para estar listos para los próximos cambios)
, necesitamos introducir algún indicador si el componente ya se inició
(en primer lugar)
.
Y solo entonces podemos comenzar la magia.
Finalmente, utilizaremos nuestro generador de componentes y su
ComponentFacotry
compilada / en caché
.
Se le pedirá a nuestro
marcador de posición de destino que cree
una instancia
del
Component
con esa 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;
//...
});
}
pequeña extensión
Además, debemos mantener una referencia a la plantilla compilada ... para poder
destroy()
correctamente, siempre que la cambiemos.
// 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;
}
}
hecho
Eso es básicamente todo.
No olvides
destruir
todo lo que se construyó dinámicamente
(ngOnDestroy)
.
Además, asegúrese de
almacenar
en
caché
los
types
y
modules
dinámicos si la única diferencia es su plantilla.
Compruébalo todo en acción aquí
para ver versiones anteriores (por ejemplo, relacionadas con RC5) de esta publicación, consulte el history
Quiero crear
dinámicamente
una
plantilla
.
Esto debería usarse para construir un
ComponentType
en
tiempo
de
ejecución
y colocarlo
(incluso reemplazarlo)
en algún lugar dentro del componente de alojamiento.
Hasta RC4 estaba usando
ComponentResolver
, pero con RC5 recibo el mensaje:
ComponentResolver
está en desuso para la compilación dinámica. UtiliceComponentFactoryResolver
junto con@NgModule/@Component.entryComponents
o ANALYZE_FOR_ENTRY_COMPONENTS proveedor en su lugar. Solo para la compilación en tiempo de ejecución , también puede usarCompiler.compileComponentSync/Async
.
Encontré este documento (oficial angular2)
Creación de componentes dinámicos síncronos angulares 2
Y entiendo que puedo usar cualquiera
-
Tipo de
ngIf
dinámico conComponentFactoryResolver
. Si voy a pasar componentes conocidos al hosting dentro de@Component({entryComponents: [comp1, comp2], ...})
, puedo usar.resolveComponentFactory(componentToRender);
-
Recopilación real en tiempo de ejecución, con
Compiler
...
Pero la pregunta es cómo usar ese
Compiler
.
La nota anterior dice que debería llamar a:
Compiler.compileComponentSync/Async
, ¿cómo?
Por ejemplo. Quiero crear (basado en algunas condiciones de configuración) este tipo de plantilla para un tipo de configuración
<form>
<string-editor
[propertyName]="''code''"
[entity]="entity"
></string-editor>
<string-editor
[propertyName]="''description''"
[entity]="entity"
></string-editor>
...
y en otro caso este
(
string-editor
se reemplaza
text-editor
)
<form>
<text-editor
[propertyName]="''code''"
[entity]="entity"
></text-editor>
...
Y así sucesivamente
(diferentes
editors
número / fecha / referencia por tipo de propiedad, omitieron algunas propiedades para algunos usuarios ...)
.
Es decir, este es un ejemplo, la configuración real podría generar plantillas mucho más diferentes y complejas.
La
plantilla está cambiando
, por lo que no puedo usar
ComponentFactoryResolver
y pasar las existentes ... Necesito una solución con el
Compiler
AOT y JitCompiler (antiguo RuntimeCompiler)
¿Le gustaría utilizar estas funciones con AOT (compilación anticipada)? Estas pillando:
Error: se encontró un error al resolver los valores de los símbolos de forma estática. Las llamadas a funciones no son compatibles. Considere reemplazar la función o lambda con una referencia a una función exportada (posición 65:17 en el archivo .ts original), resolviendo el símbolo COMPILER_PROVIDERS en ... / node_modules/@angular/compiler/src/compiler.d.ts,
Por favor, deja tu comentario, vota aquí:
¿Podría / sería / será el código usando COMPILER_PROVIDERS ser compatible con AOT?
Después de la excelente respuesta de Radmin, se necesita un pequeño ajuste para todos los que usan angular-cli versión 1.0.0-beta.22 y superior.
COMPILER_PROVIDERS
ya no se puede importar
(para más detalles, consulte
angular-cli GitHub
).
Entonces, la solución alternativa es no usar
COMPILER_PROVIDERS
y
JitCompiler
en la
providers
sección en absoluto, sino usar
JitCompilerFactory
desde ''@ angular / compilador'' en su lugar dentro de la clase de generador de tipos:
private compiler: Compiler = new JitCompilerFactory([{useDebug: false, useJit: true}]).createCompiler();
Como puede ver, no es inyectable y, por lo tanto, no tiene dependencias con el DI. Esta solución también debería funcionar para proyectos que no utilizan angular-cli.
En angular 7.x usé elementos angulares para esto.
-
Instalar @ angular-elements npm i @ angular / elements -s
-
Crear servicio de accesorios.
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;
}
}
Tenga en cuenta que su etiqueta de elemento personalizado debe ser diferente con el selector de componente angular. en AppUserIconComponent:
...
selector: app-user-icon
...
y en este caso, el nombre de la etiqueta personalizada usé "icono de usuario".
- Luego debe llamar a registrarse en AppComponent:
@Component({
selector: ''app-root'',
template: ''<router-outlet></router-outlet>''
})
export class AppComponent {
constructor(
dynamicComponents: DynamicComponentsService,
) {
dynamicComponents.register();
}
}
- Y ahora, en cualquier lugar de su código, puede usarlo así:
dynamicComponents.create(''user-icon'', {user:{...}});
o así:
const html = `<div class="wrapper"><user-icon class="user-icon" user=''${JSON.stringify(rec.user)}''></user-icon></div>`;
this.content = this.domSanitizer.bypassSecurityTrustHtml(html);
(en plantilla):
<div class="comment-item d-flex" [innerHTML]="content"></div>
Tenga en cuenta que en el segundo caso debe pasar objetos con JSON.stringify y luego analizarlo nuevamente. No puedo encontrar una mejor solución.
Este es el ejemplo de los controles dinámicos de formularios generados desde el servidor.
https://stackblitz.com/edit/angular-t3mmg6
Este ejemplo es dinámico Los controles de formulario están en el componente de agregar (Aquí es donde puede obtener los controles de formulario del servidor). Si ve el método addcomponent, puede ver los controles de formularios. En este ejemplo, no estoy usando material angular, pero funciona (estoy usando @ work). Este es el objetivo de angular 6, pero funciona en todas las versiones anteriores.
Necesita agregar JITComplierFactory para AngularVersion 5 y superior.
Gracias
Vijay
Para este caso particular, parece que usar una directiva para crear dinámicamente el componente sería una mejor opción. Ejemplo:
En el HTML donde desea crear el componente
<ng-container dynamicComponentDirective [someConfig]="someConfig"></ng-container>
Me acercaría y diseñaría la directiva de la siguiente manera.
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;
}
}
Entonces, en sus componentes, texto, cadena, fecha, lo que sea, cualquiera que sea la configuración que haya pasado en el HTML en el
ng-container
elemento estaría disponible.
La configuración,
yourConfig
puede ser la misma y definir sus metadatos.
Dependiendo de su configuración o tipo de entrada, la directiva debería actuar en consecuencia y de los tipos admitidos, representaría el componente apropiado. Si no, registrará un error.
Quiero agregar algunos detalles sobre esta excelente publicación de Radim.
Tomé esta solución y trabajé un poco en ella y rápidamente encontré algunas limitaciones. Solo describiré eso y luego daré la solución a eso también.
- En primer lugar, no pude representar el detalle dinámico dentro de un detalle dinámico (básicamente anidar interfaces de usuario dinámicas entre sí).
- El siguiente problema fue que quería renderizar un detalle dinámico dentro de una de las partes que estaba disponible en la solución. Eso tampoco fue posible con la solución inicial.
- Por último, no fue posible utilizar URL de plantilla en las partes dinámicas como el editor de cadenas.
Hice otra pregunta basada en esta publicación, sobre cómo lograr estas limitaciones, que se pueden encontrar aquí:
compilación dinámica recursiva de plantillas en angular2
Esbozaré las respuestas a estas limitaciones, si se encuentra con el mismo problema que yo, ya que eso hace que la solución sea mucho más flexible. Sería increíble tener el plunker inicial actualizado con eso también.
Para habilitar el anidado dinámico-detalle entre sí, deberá agregar DynamicModule.forRoot () en la declaración de importación en 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;
}
Además de eso, no fue posible usar
<dynamic-detail>
dentro de una de las partes que es editor de cadenas o editor de texto.
Para habilitarlo, deberá cambiar
parts.module.ts
y
dynamic.module.ts
Dentro
parts.module.ts
necesitarás agregar
DynamicDetail
el
DYNAMIC_DIRECTIVES
export const DYNAMIC_DIRECTIVES = [
forwardRef(() => StringEditor),
forwardRef(() => TextEditor),
DynamicDetail
];
También en el
dynamic.module.ts
tendría que eliminar el DynamicDetail ya que ahora son parte de las partes
@NgModule({
imports: [ PartsModule ],
exports: [ PartsModule],
})
Se puede encontrar un plunker modificado aquí: http://plnkr.co/edit/UYnQHF?p=preview (No resolví este problema, solo soy el mensajero :-D)
Finalmente, no fue posible usar templateurls en las partes creadas en los componentes dinámicos. Una solución (o solución alternativa. No estoy seguro de si es un error angular o un uso incorrecto del marco) fue crear un compilador en el constructor en lugar de inyectarlo.
private _compiler;
constructor(protected compiler: RuntimeCompiler) {
const compilerFactory : CompilerFactory =
platformBrowserDynamic().injector.get(CompilerFactory);
this._compiler = compilerFactory.createCompiler([]);
}
Luego use
_compiler
para compilar, luego templateUrls también están habilitados.
return new Promise((resolve) => {
this._compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories) =>
{
let _ = window["_"];
factory = _.find(moduleWithFactories.componentFactories, { componentType: type });
this._cacheOfFactories[template] = factory;
resolve(factory);
});
});
¡Espero que esto ayude a alguien más!
Saludos cordiales Morten
Resolvió esto en la versión Angular 2 Final simplemente usando la directiva dynamicComponent de ng-dynamic .
Uso:
<div *dynamicComponent="template; context: {text: text};"></div>
Donde template es su plantilla dinámica y el contexto se puede establecer en cualquier modelo de datos dinámico al que desee que se una su plantilla.
Sobre la base de la respuesta de Ophir Stern, aquí hay una variante que funciona con AoT en Angular 4. El único problema que tengo es que no puedo inyectar ningún servicio en DynamicComponent, pero puedo vivir con eso.
nota: no he probado con Angular 5.
import { Component, OnInit, Input, NgModule, NgModuleFactory, Compiler, EventEmitter, Output } from ''@angular/core'';
import { JitCompilerFactory } from ''@angular/compiler'';
export function createJitCompiler() {
return new JitCompilerFactory([{
useDebug: false,
useJit: true
}]).createCompiler();
}
type Bindings = {
[key: string]: any;
};
@Component({
selector: ''app-compile'',
template: `
<div *ngIf="dynamicComponent && dynamicModule">
<ng-container *ngComponentOutlet="dynamicComponent; ngModuleFactory: dynamicModule;">
</ng-container>
</div>
`,
styleUrls: [''./compile.component.scss''],
providers: [{provide: Compiler, useFactory: createJitCompiler}]
})
export class CompileComponent implements OnInit {
public dynamicComponent: any;
public dynamicModule: NgModuleFactory<any>;
@Input()
public bindings: Bindings = {};
@Input()
public template: string = '''';
constructor(private compiler: Compiler) { }
public ngOnInit() {
try {
this.loadDynamicContent();
} catch (err) {
console.log(''Error during template parsing: '', err);
}
}
private loadDynamicContent(): void {
this.dynamicComponent = this.createNewComponent(this.template, this.bindings);
this.dynamicModule = this.compiler.compileModuleSync(this.createComponentModule(this.dynamicComponent));
}
private createComponentModule(componentType: any): any {
const runtimeComponentModule = NgModule({
imports: [],
declarations: [
componentType
],
entryComponents: [componentType]
})(class RuntimeComponentModule { });
return runtimeComponentModule;
}
private createNewComponent(template: string, bindings: Bindings): any {
const dynamicComponent = Component({
selector: ''app-dynamic-component'',
template: template
})(class DynamicComponent implements OnInit {
public bindings: Bindings;
constructor() { }
public ngOnInit() {
this.bindings = bindings;
}
});
return dynamicComponent;
}
}
Espero que esto ayude.
¡Salud!
Yo mismo estoy tratando de ver cómo puedo actualizar RC4 a RC5 y, por lo tanto, me topé con esta entrada y el nuevo enfoque para la creación de componentes dinámicos todavía tiene un poco de misterio para mí, por lo que no sugeriré nada sobre la resolución de fábrica de componentes.
Pero, lo que puedo sugerir es un enfoque un poco más claro para la creación de componentes en este escenario: solo use el interruptor en la plantilla que crearía un editor de cadenas o un editor de texto de acuerdo con alguna condición, como esta:
<form [ngSwitch]="useTextarea">
<string-editor *ngSwitchCase="false" propertyName="''code''"
[entity]="entity"></string-editor>
<text-editor *ngSwitchCase="true" propertyName="''code''"
[entity]="entity"></text-editor>
</form>
Y, por cierto, "[" en la expresión [prop] tiene un significado, esto indica un enlace de datos unidireccional, por lo tanto, puede e incluso debe omitirlos en caso de que sepa que no necesita vincular la propiedad a la variable.
Debo haber llegado tarde a la fiesta, ninguna de las soluciones aquí me pareció útil, demasiado desordenada y me pareció una solución alternativa.
Lo que terminé haciendo es usar el
Angular 4.0.0-beta.6
ngComponentOutlet
.
Esto me dio la solución más corta y simple escrita en el archivo del componente dinámico.
- Aquí hay un ejemplo simple que solo recibe texto y lo coloca en una plantilla, pero obviamente puede cambiar según su necesidad:
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;
}
}
-
Breve explicación:
-
my-component
: el componente en el que se representa un componente dinámico -
DynamicComponent
: el componente que se construirá dinámicamente y se procesa dentro de mi componente
-
No olvide actualizar todas las bibliotecas angulares a ^ Angular 4.0.0
Espero que esto ayude, buena suerte!
ACTUALIZAR
También funciona para angular 5.
Tengo un ejemplo simple para mostrar cómo hacer un componente dinámico angular 2 rc6.
Supongamos que tiene una plantilla html dinámica = plantilla1 y desea cargar dinámicamente, primero envolver en el componente
@Component({template: template1})
class DynamicComponent {}
aquí template1 como html, puede contener un componente ng2
Desde rc6, debe tener @NgModule para envolver este componente. @NgModule, al igual que el módulo en anglarJS 1, desacopla diferentes partes de la aplicación ng2, entonces:
@Component({
template: template1,
})
class DynamicComponent {
}
@NgModule({
imports: [BrowserModule,RouterModule],
declarations: [DynamicComponent]
})
class DynamicModule { }
(Aquí importa RouterModule ya que en mi ejemplo hay algunos componentes de ruta en mi html como puedes ver más adelante)
Ahora puede compilar DynamicModule como:
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule).then( factory => factory.componentFactories.find(x => x.componentType === DynamicComponent))
Y necesitamos ponerlo arriba en app.moudule.ts para cargarlo, vea mi app.moudle.ts. Para obtener más detalles, consulte: https://github.com/Longfld/DynamicalRouter/blob/master/app/MyRouterLink.ts y app.moudle.ts
y vea la demostración: http://plnkr.co/edit/1fdAYP5PAbiHdJfTKgWo?p=preview
Decidí compactar todo lo que aprendí en un solo archivo . Hay mucho que ver aquí, especialmente en comparación con antes de RC5. Tenga en cuenta que este archivo fuente incluye AppModule y 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 {}`
EDITAR (26/08/2017)
: la solución a continuación funciona bien con Angular2 y 4. Lo actualicé para contener una variable de plantilla y haga clic en el controlador y lo probé con Angular 4.3.
Para Angular4, ngComponentOutlet como se describe en
la respuesta de Ophir
es una solución mucho mejor.
Pero en este momento
aún no admite entradas y salidas
.
Si se acepta [este PR] (
https://github.com/angular/angular/pull/15362]
, sería posible a través de la instancia del componente devuelta por el evento create.
ng-dynamic-component
puede ser la mejor y más simple solución, pero aún no lo he probado.
¡La respuesta de @Long Field es acertada! Aquí hay otro ejemplo (sincrónico):
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 {}
En vivo en http://plnkr.co/edit/fdP9Oc .
Respuesta de junio de 2019
¡Una gran noticia! ¡Parece que el paquete @angular/cdk ahora tiene soporte de primera clase para portals !
Al momento de escribir esto, no encontré los documentos oficiales anteriores particularmente útiles (particularmente con respecto al envío de datos y la recepción de eventos desde los componentes dinámicos). En resumen, deberá:
Paso 1) Actualice su
AppModule
Importe
PortalModule
desde el paquete
cdk
y registre sus componentes dinámicos dentro de
entryComponents
@NgModule({
declarations: [ ..., AppComponent, MyDynamicComponent, ... ]
imports: [ ..., PortalModule, ... ],
entryComponents: [ ..., MyDynamicComponent, ... ]
})
export class AppModule { }
Paso 2. Opción A: si NO necesita pasar datos y recibir eventos de sus componentes dinámicos :
@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{
}
Paso 2. Opción B: si necesita pasar datos y recibir eventos de sus componentes dinámicos :
// 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 = '''';
}