Search code examples
angularangular-cli-v6angular-library

No component factory found for undefined. Did you add it to @NgModule.entryComponents


I have created an Angular 6 components library. The library is based on PrimeNG components. One of the components in the library is a table component (that based on PrimeNG table)

To allow users of the library to add dynamic template in a table’s cell I created (in the library) a component called ‘VarintComponent’, this component has two inputs:

1) GuestComponent: String

2) GuestComponentData: any

Users of the table, can declare that a column contain VariantComponent, and pass name of a component to the GuestComponent Input. At runtime, the VariantComponent is dynamically creates and load the component using componentFactoryResolver and viewContainerRef.

import { Component, OnInit, Input, ViewChild, ComponentFactoryResolver } from '@angular/core';
import { Type } from '@angular/core';
import { VariantComponentDirective } from './variant-component.directive';

@Component({
  selector: 'o-variant',
  template: `
        <ng-template variantComponent></ng-template>
        `
})
export class VariantComponent implements OnInit {
  @Input() GuestComponent: String;
  @Input() GeustComponentData: any;

  @ViewChild(VariantComponentDirective) variantHost: VariantComponentDirective;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) { }

  ngOnInit() {
    this.loadComponent();
  }

  loadComponent() {
    const viewContainerRef = this.variantHost.viewContainerRef;
    viewContainerRef.clear();

    const factories = Array.from(this.componentFactoryResolver['_factories'].keys());
    const factoryClass = <Type<any>>factories.find((x: any) => x.name === (this.GuestComponent));
    const factory = this.componentFactoryResolver.resolveComponentFactory(factoryClass);
    const compRef = viewContainerRef.createComponent(factory);

    (<any>compRef.instance).data = this.GeustComponentData;
  }

}

The Guest Components need to implements simple interface to be hosted in the VariantComponent.

Also Guest Components are declared “entryComponents” of their modules.

When using the table in a different project, I can see that all works fine and Gust components are properly hosted in the VarintComponent and are being displayed in the table.

The problem is that when building production package (using ng build --prod) I get:

main.11fb9744a37aae5b78f8.js:1 ERROR Error: No component factory found for undefined. Did you add it to @NgModule.entryComponents?

I get this error regardless if Guest Component is located in the library or client project.

the code works well if I build using ng build --build-optimizer=false


Solution

  • Found the problem. the problem is with the following line:

    const factoryClass = <Type<any>>factories.find((x: any) => x.name === (this.GuestComponent));
    

    When packaging with --prod the package is uglyfied and class names are changed so we cannot compare by names. So we need to pass component by reference. Following code works good:

    import { Component, OnInit, Input, ViewChild, ComponentFactoryResolver } from '@angular/core';
    import { VariantComponentDirective } from './variant-component.directive';
    
    
    @Component({
      selector: 'o-variant',
      template: `
            <ng-template variantComponent></ng-template>
            `
    })
    export class VariantComponent implements OnInit {
      @Input() GuestComponent: any;
      @Input() GeustComponentData: any;
    
      @ViewChild(VariantComponentDirective) variantHost: VariantComponentDirective;
    
      constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
    
      ngOnInit() {
        this.loadComponent();
      }
    
      loadComponent() {
        const viewContainerRef = this.variantHost.viewContainerRef;
        viewContainerRef.clear();
        const factory = this.componentFactoryResolver.resolveComponentFactory(this.GuestComponent);
        const compRef = viewContainerRef.createComponent(factory);
        (<any>compRef.instance).data = this.GeustComponentData;
      }
    
    }