with typings test starter compiler angular typescript compilation angular2-compiler

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

  1. crear un módulo PartsModule:NgModule (soporte de piezas pequeñas)
  2. cree otro módulo DynamicModule:NgModule , que contendrá nuestro componente dinámico (y PartsModule referencia a PartsModule dinámicamente)
  3. crear plantilla dinámica (enfoque simple)
  4. crear un nuevo tipo de Component (solo si la plantilla ha cambiado)
  5. crear nuevo RuntimeModule:NgModule . Este módulo contendrá el tipo de Component creado previamente
  6. llame a JitCompiler.compileModuleAndAllComponentsAsync(runtimeModule) para obtener ComponentFactory
  7. crear una instancia de DynamicComponent : trabajo del marcador de posición Ver destino y ComponentFactory
  8. Asignar @Inputs a una nueva instancia (cambiar de INPUT a TEXTAREA 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 el AppModule

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 nuestro DynamicTypeBuilder , 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 y Module . 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. Utilice ComponentFactoryResolver 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 usar Compiler.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 con ComponentFactoryResolver . 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.

  1. Instalar @ angular-elements npm i @ angular / elements -s

  2. 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".

  1. Luego debe llamar a registrarse en AppComponent:

@Component({ selector: ''app-root'', template: ''<router-outlet></router-outlet>'' }) export class AppComponent { constructor( dynamicComponents: DynamicComponentsService, ) { dynamicComponents.register(); } }

  1. 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:
    1. my-component : el componente en el que se representa un componente dinámico
    2. 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{ }

Véalo en acción

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 = ''''; }

Véalo en acción