41

I'm trying to update my dynamic component loader from RC4 to RC5 since the ComponentResolver is deprecated. I've updated the loader to the following

@Component({
    selector: 'component-dispatcher',
    template: `<div #container></div>` // Define the template here because of its brevity
})
export class ComponentDispatcherComponent implements OnInit, OnDestroy {
    @Input() component:any; // Some dynamic component to render
    @Input() options:any;   // Component configuration, optional
    @Input() data:any;      // Data to render within the component

    // Inject the dynamic component onto the DOM
    @ViewChild("container", {read: ViewContainerRef}) container:ViewContainerRef;

    private componentReference:ComponentRef<any>;

    constructor(private resolver:ComponentFactoryResolver) {
    }

    ngOnInit() {
        // Create our component now we're initialised
        let componentFactory = this.resolver.resolveComponentFactory(this.component);
        this.componentReference = this.container.createComponent(componentFactory);
        this.componentReference.instance.data = this.data;
        this.componentReference.instance.options = this.options;
    }

    ngOnDestroy() {
        // If we have a component, make sure we destroy it when we lose our owner
        if (this.componentReference) {
            this.componentReference.destroy();
        }
    }
}

And attempt to dynamically load the following component into the DOM

@Component({
    selector: 'text-cell',
    pipes: [IterableObjectPipe],
    templateUrl: './text-cell.component.html',
    styles: ['.fieldName { font-weight: bold; }']
})
export class TextCellComponent implements OnInit {
    // Data to render within the component
    @Input() data: any;
    @Input() record: any;

    // Configuration of what data to display
    @Input() options: {
        excludeFieldNames: boolean,
        translation: string
    };

    constructor() {
    }

    ngOnInit() {
        setTimeout(() => {
            //console.log('***************************** ngOnInit...textCell ***********************');
            this.options.translation = '' + (_.get(this.options, 'translation') || 'fields');
        });
    }
}

Yet when I do this with my TextCellComponent or any other component within the app I get the following error

ORIGINAL EXCEPTION: No component factory found for TextCellComponent
ORIGINAL STACKTRACE:
Error: No component factory found for TextCellComponent
at NoComponentFactoryError.BaseException [as constructor]      
(webpack:///./~/@angular/core/src/facade/exceptions.js?:27:23)
at new NoComponentFactoryError 

I've completed the steps in

https://angular.io/docs/ts/latest/cookbook/rc4-to-rc5.html

but I seem to be missing something. I've tried adding the components to the bootstrapping and defining them globally with no luck. Any suggestions would be helpful.

EDIT

Adding the module definition

@NgModule({
    imports: [
        BrowserModule, 
        HttpModule, 
        FormsModule, 
        ReactiveFormsModule, 
        ...MATERIAL_MODULES
    ],
    declarations: [
        ...APPLICATION_PIPES, 
        ...APPLICATION_COMPONENTS, 
        ...APPLICATION_DIRECTIVES, 
        CygnusComponent,
        // Component declarations
        // TODO: refactor to appropriate modules
        ...
        ComponentDispatcherComponent,
        TextCellComponent,
        ...
    ],
    bootstrap: [
        ApplicationComponent
    ],
    providers: [
        ...APPLICATION_PROVIDERS, 
        AppStore
    ]
})
export class ApplicationComponent {}
Danny Bullis
  • 3,043
  • 2
  • 29
  • 35
JME
  • 2,293
  • 9
  • 36
  • 56

5 Answers5

98

All components about to be loaded "dynamically" need to be declared in the entryComponents section of your module. In other words you should end up with something like:

@NgModule({
    imports: [BrowserModule, HttpModule, FormsModule, ReactiveFormsModule, ...MATERIAL_MODULES],
    declarations: [...APPLICATION_PIPES, ...APPLICATION_COMPONENTS, ...APPLICATION_DIRECTIVES, CygnusComponent,
        // Component declarations
        // TODO: refactor to appropriate modules
        ...
        ComponentDispatcherComponent,
        TextCellComponent,
        ...
    entryComponents: [TextCellComponent]
    bootstrap: [ApplicationComponent],
    providers: [...APPLICATION_PROVIDERS, AppStore]
})
export class ApplicationComponent{

Please note that you need to list the TextCellComponent in both the declarations and entryComponents section.

pkozlowski.opensource
  • 117,202
  • 60
  • 326
  • 286
  • 5
    What if my component is there already and I still get the issue ? – silvio Aug 30 '18 at 14:54
  • Still relevant in Angular 7, just spend 4 hours searching until I realized I had to add the `entryComponents`. - It's all in the official Angular tutorial really. It's just hard to follow tutorials without skipping parts. – bvdb Mar 28 '19 at 17:10
1

You may want to check your import paths. In my case, one file import used uppercase while the other used lowercase.

//file 1
import { IRComponent } from "./components/IR/IR.component";
//file 2
import { IRComponent } from "./components/ir/ir.component";

The give away was in the Chrome network tab, I noticed that the file was being loaded twice (once for each spelling).

Alan
  • 140
  • 9
1

Let's say TextCellComponent is declared in FooModule and your component responsible for creating dynamic content is in module BarModule.

In such case FooModule needs to be imported into BarModule

@NgModule({
 imports: [FooModule],
declarations: [ComponentDispatcherComponent]
})
export class BarModule {}

To my eyes that kinda compromises the idea of things being dynamic. I simply want a component that will create any component I send it to by class reference. If anyone has a decent solution I'd be glad to hear it.

George Knap
  • 843
  • 9
  • 30
0

There are sometimes when you will face this issue even if you have given the Component in your EntryComponents as well as Declarations.

In those situations, you just have to enter this component name (here TextCellComponent) above all other components as below:

declarations: [ 
        TextCellComponent // Declared above
        CygnusComponent,
        ComponentDispatcherComponent,
        ...
    ]

This should also be done in the entryComponents.

Hope that helps.

gsthina
  • 1,090
  • 8
  • 22
-1

You need to import MatDialogModule in Module in order for it to know about the entryComponents there.

Nebojsa Sapic
  • 9,285
  • 1
  • 22
  • 23