Quiero crear dinámicamente una plantilla. Esto debería usarse para construir un ComponentType
tiempo de ejecución y colocarlo (incluso reemplazarlo) en algún lugar dentro del Componente de alojamiento.
Hasta RC4 que estaba usando ComponentResolver
, pero con RC5 recibo el siguiente mensaje:
ComponentResolver is deprecated for dynamic compilation.
Use ComponentFactoryResolver together with @NgModule/@Component.entryComponents or ANALYZE_FOR_ENTRY_COMPONENTS provider instead.
For runtime compile only, you can also use Compiler.compileComponentSync/Async.
Encontré este documento ( Creación de componentes dinámicos síncronos angulares 2 )
Y entiendo que puedo usar cualquiera
- Tipo de dinámica
ngIf
conComponentFactoryResolver
. Si paso componentes conocidos 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 eso Compiler
. La nota anterior dice que debería llamar: Compiler.compileComponentSync/Async
¿y 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 por text-editor
)
<form>
<text-editor
[propertyName]="'code'"
[entity]="entity"
></text-editor>
...
Y así sucesivamente (diferente número / fecha / referencia editors
por tipo de propiedad, se 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 Compiler
.
fuente
$compile
podría hacer que estos métodos no puedan: estoy creando una aplicación en la que solo quiero compilar el HTML a medida que entra a través de la página de un tercero y llamadas ajax. No puedo eliminar el HTML de la página y colocarlo en mi propia plantilla. SuspiroRespuestas:
EDITAR - relacionado con 2.3.0 (2016-12-07)
Se discute un tema similar aquí Equivalente de $ compilar en Angular 2 . Necesitamos usar
JitCompiler
yNgModule
. Lea más sobreNgModule
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) buscar
ComponentFactory
en caché - ir a 7)3) - crear
Component
4) - crear
Module
5) - compilar
Module
6) - devolver (y caché para uso posterior)
ComponentFactory
7) usar Target y
ComponentFactory
crear una instancia de dinámicaComponent
Aquí hay un fragmento de código (más de esto aquí ) : nuestro generador personalizado regresa recién construido / almacenado en caché
ComponentFactory
y la vista Marcador de posición de destino consume para crear una instancia delDynamicComponent
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
PartsModule:NgModule
(titular de piezas pequeñas)DynamicModule:NgModule
, que contendrá nuestro componente dinámico (y referenciaráPartsModule
dinámicamente)Component
tipo (solo si la plantilla ha cambiado)RuntimeModule:NgModule
. Este módulo contendrá elComponent
tipo creado previamenteJitCompiler.compileModuleAndAllComponentsAsync(runtimeModule)
para obtenerComponentFactory
DynamicComponent
trabajo - del marcador de posición Ver destino yComponentFactory
@Inputs
a nueva instancia (cambio deINPUT
aTEXTAREA
la edición) , consumen@Outputs
NgModule
Necesitamos un
NgModule
s.Habrá un módulo para todos los componentes pequeños, por ejemplo
string-editor
,text-editor
(date-editor
,number-editor
...)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 forma estándar, con
forRoot()
Finalmente, necesitaremos un módulo de tiempo de ejecución ad hoc ... pero que se creará más adelante, como parte del
DynamicTypeBuilder
trabajo.El cuarto módulo, módulo de aplicación, es el que mantiene declara proveedores de compiladores:
Lea (lea) mucho más sobre NgModule allí:
Un generador de plantillas
En nuestro ejemplo procesaremos detalles de este tipo de entidad.
Para crear un
template
, en este plunker usamos este constructor simple / ingenuo.Un truco aquí es: crea una plantilla que utiliza un 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.
Un
ComponentFactory
constructorLo muy importante aquí es tener en cuenta:
Entonces, estamos tocando el núcleo de nuestra solución. The Builder, 1) creará
ComponentType
2) creará suNgModule
3) compilaráComponentFactory
4) la caché para su posterior reutilización.Una dependencia que necesitamos recibir:
Y aquí hay un fragmento de cómo obtener un
ComponentFactory
: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
Importante:
ComponentFactory
utilizado por el componente de alojamientoLa pieza final es un componente, que aloja el objetivo de nuestro componente dinámico, por ejemplo
<div #dynamicContentPlaceHolder></div>
. Obtenemos una referencia y lo usamosComponentFactory
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:
Acabamos de recibir, constructores de plantillas y componentes. Luego están las propiedades que se necesitan para nuestro ejemplo (más en comentarios)
En este escenario simple, nuestro componente de alojamiento no tiene ninguno
@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, usaremos nuestro generador de componentes, y simplemente se compilará / almacenará en caché
ComponentFacotry
. Nuestro marcador de posición de destino se le pedirá crear una instancia de laComponent
con la fábrica.pequeña extensión
Además, debemos mantener una referencia a la plantilla compilada ... para poder usarla correctamente
destroy()
, siempre que la cambiemos.hecho
Eso es básicamente todo. No olvides destruir todo lo que se creó dinámicamente (ngOnDestroy) . Además, asegúrese de almacenar en caché dinámico
types
ymodules
si la única diferencia es su plantilla.Compruébalo todo en acción aquí
fuente
type.builder.ts
como usted ha señalado, deseo, que cualquier usuario entienda cómo colocar todo eso en contexto ... Espero que pueda ser útil;)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 todavía 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 el mejor y más simple solución por completo, pero aún no lo he probado.
¡La respuesta de @Long Field es acertada! Aquí hay otro ejemplo (sincrónico):
En vivo en http://plnkr.co/edit/fdP9Oc .
fuente
ngAfterViewInit
llamada con aconst template
no funcionará. Pero si su tarea era reducir el enfoque descrito anteriormente detallado (crear plantilla, crear componente, crear módulo, compilarlo, crear fábrica ... crear instancia) ... probablemente lo hizoDebo 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 utilizar
Angular 4.0.0-beta.6
's ngComponentOutlet .Esto me dio la solución más corta y simple escrita en el archivo del componente dinámico.
my-component
- el componente en el que se representa un componente dinámicoDynamicComponent
- el componente que se construirá dinámicamente y se renderiza dentro de my-componentNo olvide actualizar todas las bibliotecas angulares a ^ Angular 4.0.0
Espero que esto ayude, buena suerte!
ACTUALIZAR
También funciona para angular 5.
fuente
Respuesta de junio de 2019
¡Una gran noticia! ¡Parece que el paquete @ angular / cdk ahora tiene soporte de primera clase para portales !
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) Actualiza tu
AppModule
Importa
PortalModule
desde el@angular/cdk/portal
paquete y registra tus componentes dinámicos dentroentryComponents
Paso 2. Opción A: si NO necesita pasar datos y recibir eventos de sus componentes dinámicos :
Véalo en acción
Paso 2. Opción B: si necesita pasar datos y recibir eventos de sus componentes dinámicos :
Véalo en acción
fuente
Portal
difiere este enfoque dengTemplateOutlet
yngComponentOutlet
? 🤔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.
fuente
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
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, por lo que:
(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
fuente
En angular 7.x usé elementos angulares para esto.
Instalar @ angular-elements npm i @ angular / elements -s
Crear servicio de accesorios.
Tenga en cuenta que su etiqueta de elemento personalizado debe ser diferente con el selector de componente angular. en AppUserIconComponent:
y en este caso, el nombre de la etiqueta personalizada usé "icono de usuario".
o así:
(en plantilla):
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.
fuente
document.createElement(tagName);
Resolvió esto en la versión Angular 2 Final simplemente usando la directiva dynamicComponent de ng-dynamic .
Uso:
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.
fuente
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.
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
Solo describiré 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 anidamiento dinámico de detalles entre sí, deberá agregar DynamicModule.forRoot () en la declaración de importación en type.builder.ts
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
ydynamic.module.ts
En el interior
parts.module.ts
deberás agregarDynamicDetail
elDYNAMIC_DIRECTIVES
También en el
dynamic.module.ts
tendría que eliminar el DynamicDetail ya que ahora son parte de las partesPuede 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 utilizar 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.
Luego use
_compiler
para compilar, luego templateUrls también están habilitados.¡Espero que esto ayude a alguien más!
Saludos cordiales Morten
fuente
Siguiendo 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
yJitCompiler
en laproviders
sección, sino usarJitCompilerFactory
desde '@ angular / compilador' en su lugar dentro de la clase de generador de tipos: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 usan angular-cli.
fuente
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:
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.
fuente
switch
/case
contiene pocas decisiones. Pero imagine que la plantilla generada podría ser realmente grande ... y diferir para cada entidad, diferir por seguridad, diferir por estado de entidad, por cada tipo de propiedad (número, fecha, referencia ... editores) ... En tal caso, resolver esto en una plantilla htmlngSwitch
crearía unhtml
archivo grande, muy muy grande .Este es el ejemplo de 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
fuente
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
Me acercaría y diseñaría la directiva de la siguiente manera.
Entonces, en sus componentes, texto, cadena, fecha, lo que sea, sea cual 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.
fuente
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.
Espero que esto ayude.
¡Salud!
fuente