Carga dinámica de componentes

November 29, 2018 · View on GitHub

En este tema veremos cómo utilizar ComponentFactoryResolver para añadir componentes a una vista dinámicamente.

Elementos previos

Los anuncios

ng g c components/ad1
ng g c components/ad2
ng g c components/ad3
ng g c components/ad4
export class Ad1Component implements AdComponent {

  data: any
  constructor() { }

  ngOnInit() {
  }

}
<h2>{{data.headline}}</h2>
<div>{{data.body}}</div>
import { Type } from '@angular/core';

export class AdItem {
  constructor(public component: Type<any>, public data: any) {}
}

El servicio

@Injectable()
export class AdService {
  getAds() {
    return [
      new AdItem(Ad1Component, {headline: 'Bombasto', body: 'Brave as they come'}),

      new AdItem(Ad2Component, {headline: 'Dr IQ', body: 'Smart as they come'}),

      new AdItem(Ad3Component,   {headline: 'Hiring for several positions',
                                        body: 'Submit your resume today!'}),

      new AdItem(Ad4Component,   {headline: 'Openings in all departments',
                                        body: 'Apply today'}),
    ];
  }
}

EntryComponents

Lo primero de todo es que hay que declarar los componentes dinámicos en la propiedad entryComponents de los metadatos del decoraddor @NgModule de nuestro módulo.

import { BrowserModule }        from '@angular/platform-browser';
import { NgModule }             from '@angular/core';
import { AppComponent }         from './app.component';
import { HeroJobAdComponent }   from './hero-job-ad.component';
import { AdBannerComponent }    from './ad-banner.component';
import { HeroProfileComponent } from './hero-profile.component';
import { AdDirective }          from './ad.directive';
import { AdService }            from './ad.service';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [
    AdsLoaderComponent,
    Ad1Component,
    Ad2Component,
    Ad3Component,
    Ad4Component,
  ],
  entryComponents: [
    Ad1Component,
    Ad2Component,
    Ad3Component,
    Ad4Component
  ],
  providers: [
    AdService
  ],
  exports: [
    AdsLoaderComponent
  ]
})
export class DynamicComponentsModule {
  constructor() {}
}

Paso 1: Incluir ng-template con una template variable

En src/app/ad-banner.component.html:

<div class="ad-banner">
  <h3>Advertisements</h3>
  <ng-template #adhost></ng-template>
</div>

El elemento <ng-template> es una buena elección para componentes dinámicos porque no renderiza nada por sí mismo.

Paso 2: Programar la carga dinámica

//src/app/ad-banner.component.ts
export class AdsLoaderComponent implements OnInit {

  @Input() ads: AdItem[];
  currentAdIndex = -1;
  @ViewChild('adhost', {read: ViewContainerRef}) viewContainerRef: ViewContainerRef;
  interval: any;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) { }

  ngOnInit() {
    this.loadComponent();
    this.getAd();
  }

  ngOnDestroy() {
    clearInterval(this.interval);
  }

  loadComponent() {
    this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
    let adItem = this.ads[this.currentAdIndex];

    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);

    this.viewContainerRef.clear();

    let componentRef = this.viewContainerRef.createComponent(componentFactory);
    (<AdComponent>componentRef.instance).data = adItem.data;
  }

  getAd() {
    this.interval = setInterval(() => {
      this.loadComponent();
    }, 3000);
  }
}

Este componente, cada 3 segundos, selecciona un componente y utiliza el servicio ComponentFactoryResolver para resolver un ComponentFactory por cada componente específico. El ComponentFactory entonces crea una instancia de cada componente.

Obtenemos una referencia al viewContainerRef gracias al conocido decorador @ViewChild y llamamos al método createComponent().

https://angular.io/api/core/ViewChild

https://angular.io/api/core/ViewContainerRef

El método createComponent() devuelve una referencia al componente recién cargado. Gracias a esa referencia, podemos acceder a las propiedades y métodos del componente.

Consejo

Si hacemos que los componentes cargados dinámicamente cumplan un Interface, nos aseguramos que todos ellos tengan las propiedades y/o métodos que necesitemos.

export interface AdComponent {
  data: any;
}