javascript angular

javascript - Angular 2.1.0 crea componentes secundarios sobre la marcha, dinĂ¡micamente



(1)

Puede usar la siguiente directiva HtmlOutlet :

import { Component, Directive, NgModule, Input, ViewContainerRef, Compiler, ComponentFactory, ModuleWithComponentFactories, ComponentRef, ReflectiveInjector } from ''@angular/core''; import { RouterModule } from ''@angular/router''; import { CommonModule } from ''@angular/common''; export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> { const cmpClass = class DynamicComponent {}; const decoratedCmp = Component(metadata)(cmpClass); @NgModule({ imports: [CommonModule, RouterModule], declarations: [decoratedCmp] }) class DynamicHtmlModule { } return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule) .then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => { return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp); }); } @Directive({ selector: ''html-outlet'' }) export class HtmlOutlet { @Input() html: string; cmpRef: ComponentRef<any>; constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { } ngOnChanges() { const html = this.html; if (!html) return; if(this.cmpRef) { this.cmpRef.destroy(); } const compMetadata = new Component({ selector: ''dynamic-html'', template: this.html, }); createComponentFactory(this.compiler, compMetadata) .then(factory => { const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector); this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []); }); } ngOnDestroy() { if(this.cmpRef) { this.cmpRef.destroy(); } } }

Ver también Ejemplo de Plunker

Ejemplo con componente personalizado

Para la compilación AOT ver estos hilos

Consulte también el ejemplo de AOT de github Webpack https://github.com/alexzuza/angular2-build-examples/tree/master/ngc-webpack

Lo que estoy tratando de hacer en angular 2.1.0 es crear componentes secundarios sobre la marcha que deberían inyectarse en el componente principal. Por ejemplo, el componente principal es lessonDetails que contiene cosas compartidas para todas las lecciones, como botones como Go to previous lesson , Go to next lesson y otras cosas. Según los parámetros de ruta, el contenido de la lección, que debe ser un componente secundario, debe inyectarse dinámicamente en el componente principal . HTML para componentes secundarios (contenido de la lección) se define como una cadena simple en algún lugar externo, puede ser un objeto como:

export const LESSONS = { "lesson-1": `<p> lesson 1 </p>`, "lesson-2": `<p> lesson 2 </p>` }

El problema se puede resolver fácilmente a través de innerHtml tiene algo como seguir en la plantilla del componente principal .

<div [innerHTML]="lessonContent"></div>

Donde en cada cambio de parámetros de ruta, la lessonContent de propiedad El lessonContent del componente principal cambiaría (el contenido (nueva plantilla) se tomaría del objeto LESSON ) haciendo que la plantilla del componente principal se actualice. Esto funciona, pero angular no procesará el contenido inyectado a través de innerHtml por lo que es imposible usar routerLink y otras cosas.

Antes de la nueva versión angular resolví este problema usando la solución de http://blog.lacolaco.net/post/dynamic-component-creation-in-angular-2/ , donde he estado usando ComponentMetadata junto con ComponentResolver para crear componentes secundarios en la mosca, como:

const metadata = new ComponentMetadata({ template: this.templateString, });

Donde templateString se pasó al componente secundario como propiedad de Input al componente secundario. Tanto MetaData como ComponentResolver están en desuso / eliminados en angular 2.1.0 .

Entonces, el problema no se trata solo de la creación dinámica de componentes, como se describe en algunas preguntas SO relacionadas, el problema sería más fácil de resolver si hubiera definido un componente para cada contenido de la lección. Esto significaría que necesito predeclarar 100 componentes diferentes para 100 lecciones diferentes. Los metadatos en desuso proporcionaron un comportamiento que era como actualizar la plantilla en tiempo de ejecución de un solo componente (crear y destruir un solo componente en el cambio de parámetros de ruta).

Actualización 1: Como parece en la versión angular reciente, todos los componentes que deben crearse / inyectarse dinámicamente deben predefinirse en entryComponents dentro de @NgModule . Entonces, como me parece, en relación con la pregunta anterior, si necesito tener 100 lecciones (componentes que deben crearse dinámicamente sobre la marcha), eso significa que necesito predefinir 100 componentes

Actualización 2: según la Actualización 1, se puede hacer a través de ViewContainerRef.createComponent() de la siguiente manera:

// lessons.ts @Component({ template: html string loaded from somewhere }) class LESSON_1 {} @Component({ template: html string loaded from somewhere }) class LESSON_2 {} // exported value to be used in entryComponents in @NgModule export const LESSON_CONTENT_COMPONENTS = [ LESSON_1, LESSON_2 ]

Ahora en el componente principal en el cambio de parámetros de ruta

const key = // determine lesson name from route params /** * class is just buzzword for function * find Component by name (LESSON_1 for example) * here name is property of function (class) */ const dynamicComponent = _.find(LESSON_CONTENT_COMPONENTS, { name: key }); const lessonContentFactory = this.resolver.resolveComponentFactory(dynamicComponent); this.componentRef = this.lessonContent.createComponent(lessonContentFactory);

La plantilla principal se ve así:

<div *ngIf="something" #lessonContentContainer></div>

Donde lessonContentContainer está decorada @ViewChildren propiedad @ViewChildren y lessonContent está decorada como @ViewChild y se inicializa en ngAfterViewInit () como:

ngAfterViewInit () { this.lessonContentContainer.changes.subscribe((items) => { this.lessonContent = items.first; this.subscription = this.activatedRoute.params.subscribe((params) => { // logic that needs to show lessons }) }) }

La solución tiene un inconveniente y es que todos los componentes (LESSON_CONTENT_COMPONENTS) deben estar predefinidos.
¿Hay alguna manera de usar un solo componente y cambiar la plantilla de ese componente en tiempo de ejecución (en el cambio de parámetros de ruta)?