reactivos - Angular2: inserte un componente dinámico como hijo de un contenedor en el DOM
tablas angular 4 (4)
De forma sucia, solo guarde la referencia del componente creado dinámicamente (hermano) y muévalo con vanilla JS:
constructor(
private el: ElementRef,
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver,
) {}
ngOnInit() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
MyDynamicComponent,
);
const componentRef = this.viewContainerRef.createComponent(componentFactory);
this.el.nativeElement.appendChild(componentRef.location.nativeElement);
}
¿Hay una manera de insertar dinámicamente un componente como un elemento secundario (no un hermano) de una etiqueta DOM en Angular 2?
Hay muchos ejemplos por ahí para insertar un componente dinámico como hermano de una etiqueta de ViewContainerRef
dada, como (a partir de RC3):
@Component({
selector: ''...'',
template: ''<div #placeholder></div>''
})
export class SomeComponent {
@ViewChild(''placeholder'', {read: ViewContainerRef}) placeholder;
constructor(private componentResolver: ComponentResolver) {}
ngAfterViewInit() {
this.componentResolver.resolveComponent(MyDynamicComponent).then((factory) => {
this.componentRef = this.placeholder.createComponent(factory);
});
}
}
Pero esto genera un DOM similar a:
<div></div>
<my-dynamic-component></my-dynamic-component>
Resultado Esperado:
<div>
<my-dynamic-component></my-dynamic-component>
</div>
El uso de SomeComponent
''s ViewContainerRef
tiene el mismo resultado, todavía está insertando el componente generado como un hermano, no como un niño. Estaría de acuerdo con una solución donde la plantilla esté vacía y los componentes dinámicos se inserten en la plantilla (dentro de la etiqueta selectora de componentes).
La estructura DOM es muy importante cuando se usan bibliotecas como ng2-dragula para arrastrar desde una lista de componentes dinámicos y beneficiarse de las actualizaciones del modelo . El div adicional está en la lista de elementos arrastrables, pero fuera del modelo, rompiendo la lógica de arrastrar y soltar.
Algunos dicen que no es posible (ver este comentario ), pero parece una limitación muy sorprendente.
Estaba buscando una solución para el mismo problema y la respuesta aprobada no funcionó para mí. Pero he encontrado una solución mucho mejor y más lógica de cómo agregar un componente creado dinámicamente como elemento secundario al elemento del host actual.
La idea es usar la referencia de elemento del componente recién creado y agregarlo a la referencia de elemento actual mediante el uso del servicio de representación. Podemos obtener el objeto componente a través de su propiedad inyector.
Aquí está el código:
@Directive({
selector: ''[mydirective]''
})
export class MyDirectiveDirective {
constructor(
private cfResolver: ComponentFactoryResolver,
public vcRef: ViewContainerRef,
private renderer: Renderer2
) {}
public appendComponent() {
const factory =
this.cfResolver.resolveComponentFactory(MyDynamicComponent);
const componentRef = this.vcRef.createComponent(factory);
this.renderer.appendChild(
this.vcRef.element.nativeElement,
componentRef.injector.get(MyDynamicComponent).elRef.nativeElement
);
}
}
My Solution sería bastante similar a @Bohdan Khodakivskyi. Pero lo intenté con el Renderer2.
constructor(
private el: ElementRef,
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver,
private render: Renderer2
) {}
ngOnInit() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
MyDynamicComponent,
);
const componentRef = this.viewContainerRef.createComponent(componentFactory);
this.render.appendChild(this.el.nativeElement, componentRef.location.nativeElement)
}
TL; DR : reemplaza <div #placeholder></div>
por <div><ng-template #placeholder></ng-template></div>
para insertar dentro de la div
.
Aquí hay un ejemplo de stackblitz de trabajo (Angular 6), y el código relevante:
@Component({
selector: ''my-app'',
template: `<div><ng-template #container></ng-template></div>`
})
export class AppComponent implements OnInit {
@ViewChild(''container'', {read: ViewContainerRef}) viewContainer: ViewContainerRef;
constructor(private compiler: Compiler) {}
ngOnInit() {
this.createComponentFactory(MyDynamicComponent).then(
(factory: ComponentFactory<MyDynamicComponent>) => this.viewContainer.createComponent(factory),
(err: any) => console.error(err));
}
private createComponentFactory(/*...*/) {/*...*/}
}
Parece que <ng-container #placeholder></ng-container>
también está funcionando (reemplace ng-template
por ng-container
). Me gusta este enfoque porque <ng-container>
está claramente dirigido a este caso de uso (un contenedor que no agrega una etiqueta) y se puede usar en otras situaciones como NgIf
sin envolver en una etiqueta real.
PD: @ GünterZöchbauer me dirigió a la discusión correcta en un comentario, y finalmente respondí mi propia pregunta.
Editar [2018-05-30]: actualizado al enlace de stackblitz para tener un ejemplo actualizado y en funcionamiento.