angularcomponenteselementrefng-containerdinamicodinámicotemplate reference variable/@ViewChildViewChildservicioresolveComponentFactoryfactoryentryComponentsViewContainerRefComponentFactoryResolverinstancecreateComponentresolveComponentFactory
jolugama.com

Angular 9 - Componentes dinámicos, sin entryComponents

Publicado: por

Guia para hacer componentes dinámicos en Angular 9, una manera más rápida y limpia, con menos lineas y sin tocar los módulos.

Ha cambiado ligeramente la manera de incorporar componentes dinámicos a nuestro Angular con la nueva llegada de la versión 9. Nos olvidamos de añadir al módulo mediante entryComponents los componentes que se van a incorporar dinámicamente después. Ahora todo se carga en caliente desde el componente mediante async await.

1. Elegir dónde cargar el componente dinámico

No ha cambiado nada. Justo donde se quiere añadir dicho componente dinámico, añadir una template reference variable para tener una referencia al elemento.

En el html:

...
<ng-container #componenteDinamico></ng-container>

Podría ser un div, pero se crearía un padre div estuviera creado o no dicho componente dinámico. Para eliminar ese div innecesario.

Todo lo demás se hace en el ts.

  • Importa los componentes a referenciar.
  • Crea una referencia a este elemento con ViewChild de tipo ViewContainerRef.

En el ts:

export class ItemsListComponent implements OnInit, OnDestroy, AfterViewInit {
  miFactory: ComponentFactory<any>;
  componentRef: ComponentRef<miDinamicoComponent> = null; // se declara una variable referencia.
  componentRef2: ComponentRef<miDinamicoComponent2> = null; // se declara una variable referencia.
  // ViewContainerRef crea componentes en su interior de forma dinámica
  // Hay que añadir read: ViewContainerRef, ya que si no, devolvería un ElementRef,
  // que es lo que devuelve por defecto un viewChild.
  // la referencia que tenemos es compDynamicContainer, y en ese contenedor añadiremos los componentes dinámicos
  @ViewChild('componenteDinamico', { read: ViewContainerRef }) compDynamicContainer: ViewContainerRef;

Si lo que queremos es cambiar toda la página, no hace falta entonces viewchild. solo habría que añadir en el constructor:

  constructor(
    ...
    private compDynamicContainer: ViewContainerRef,
  ) { }

2. Fabricador de componentes dinámicos: ComponentFactoryResolver

Esta parte tampoco ha cambiado. Inserta el servicio ComponentFactoryResolver, el cual permite fabricar componentes de forma dinámica.

En el constructor:

  constructor(
    ...
    private resolver: ComponentFactoryResolver
  ) { }

3. Crear factoría y visualizar componente

Es aquí donde cambia todo en Angular 9. Imagina 2 botones, dos funciones async que cada uno carga un componente distinto, y 2 botones, cada uno ejecuta una función async. Pues cambiando esos botones se cambiaría el componente, en la zona del dom donde refleja viewchild.

Crea una factoría para el componente dinámico, que permita instanciarlo en el elemento contenedor creado anteriormente (compDynamicContainer).

  ngAfterViewInit() {
    this.getLazyComponent();
  }

  async getLazyComponent() {
    this.compDynamicContainer.clear();
    const { MiComponenteComponent } = await import('@miscomponentes/components/mi-componente/mi-componente.component');
    this.componentRef = this.compDynamicContainer.createComponent(
      this.resolver.resolveComponentFactory(MiComponenteComponent)
    );
  }

En este punto el componente se ha creado y se está visualizando. Si se quiere cargar en otro momento, incorporarlo en una función en vez de ngAfterViewInit.

4. Acceso a los métodos y propiedades públicos

Una vez guardada la referencia de la creación del componente, se puede acceder a dicho componente, ejecutar métodos públicos y acceder a sus propiedades.

Todo esto gracias a instance.

this.componentRef.instance.miFuncionSuma(15,2)
this.componentRef.instance.titulo='Cambio el titulo del componente';
this.componentRef.instance.mensaje='cambio el mensaje también';

También se puede acceder e incorporar a cualquier @Input, ya que es público. En el caso de los @Output, nos podemos subscribir a ellos:

this.componentRef.instance.miEventEmiter(()=>{
  console.log('se ejecuta el event emiter');
});

5. Destruir componente dinámico

No ha cambiado nada de la última versión.

Supongamos que tenemos un contenedor compDynamicContainer, y queremos que en un primer momento se pinte un componente, pero que a los x segundos, o al pulsar cualquier acción, se elimine, o se cambie por otro componente. Esto puede ser si el componente dinámico es un modal, o tenemos un sistema de paginado con componentes dinámicos.

Esto se hace con destroy(). Un ejemplo:

ngAfterViewInit() {
  this.getLazyComponent(); 

  setTimeout(() => {
    this.getLazy2Component();
  }, 2000);
}

async getLazyComponent() {
  this.componentRef.destroy();
  this.compDynamicContainer.clear();
  const { miDinamicoComponent } = await import('@miscomponentes/components/mi-componente/mi-componente.component');
  this.componentRef = this.compDynamicContainer.createComponent(
    this.resolver.resolveComponentFactory(AntsListComponent)
  );
  this.componentRef.instance.openCard(24);
}

async getLazy2Component() {
  this.componentRef.destroy();
  this.compDynamicContainer.clear();
  const { miDinamicoComponent } = await import('@miscomponentes/components/mi-componente/mi-componente2.component');
  this.componentRef = this.compDynamicContainer.createComponent(
    this.resolver.resolveComponentFactory(AntsListComponent)
  );
  this.componentRef.instance.card(10);
}
 
... y finalmente... Si te ha gustado, difúndelo. Es solo un momento. escríbe algo en comentarios 😉 Gracias.