Adding dynamic components in a NgFor

Viewed 2650

I would like to create a component which takes another one in parameter, and generate a collection of instances of this component.

(The objective in the end is to create a generic backoffice).

For example, with something like that :

<common-collection [component]="collectionDisplayComponent" [items]="items"></common-collection>

I would get this (here the component is a loader for test purpose) :

enter image description here

So I already have something working (almost): Here the code of the component :

@Component({
  selector: 'common-collection',
  templateUrl: 'collection.component.html',
  styleUrls:  ['collection.component.css'],
  providers: [  ]

})
export class CollectionComponent implements AfterViewInit{

    constructor(private componentFactoryResolver: ComponentFactoryResolver) { }

    @Input()
    set items(items: Array<object>){
      this._items = items;
      this.refresh();
    }

    private _items: Array<object> = [];screen
    @ViewChildren('componentRef', {read: ViewContainerRef}) public widgetTargets: QueryList<ViewContainerRef>

    public refresh() {
      let component = LoaderComponent; // Should be given in parameter, set manually for now

      if(typeof(this.widgetTargets) !== "undefined"){

        for (let i = 0; i < this.widgetTargets.toArray().length; i++) {
            let target = this.widgetTargets.toArray()[i];
            let widgetComponent = this.componentFactoryResolver.resolveComponentFactory(component);
            let cmpRef: any = target.createComponent(widgetComponent);


        }
      }
    }

    ngAfterViewInit(): void {
      this.widgetTargets.changes.subscribe(() => {
        this.refresh()
      });
    }
}

And the template code associated :

<div id="div-container">
    <div fxLayout="row" fxLayoutWrap fxLayoutGap="1%" fxFlexAlign="center center">
        <div class="div-item-card" fxFlex="24"  *ngFor="let item of _items;">
            <div #componentRef></div>
        </div>
    </div>
</div>

This code works, but the problem is that I'm setting the viewChildren at the wrong time, because I have this error :

enter image description here

I think I should fill the widgetTargets variable and do the ngFor on it instead of _items, but I don't find any example to add components directly in it.

All the examples consist in creating "void" dom elements, and setting them with the component factory after that (which I do there and causes the error).

Thanks a lot for your help.

EDIT : The "items" variable is set after the component init : it is loaded from an async call to a database in a parent component.

EDIT2 Working with something like this in a refresh method.

for (let i = 0; i < this.widgetTargets.toArray().length; i++) {
            let target = this.widgetTargets.toArray()[i];
            let widgetComponent = this.componentFactoryResolver.resolveComponentFactory(component);
            target.clear();
            let cmpRef: any = target.createComponent(widgetComponent);
            cmpRef.instance.value = this._items[i]["value"];
            this.cd.detectChanges();
        }
3 Answers

It will help you for render the component dynamically from typescript

    var data=[{"cardStyle1":LineChartComponent},{"cardStyle2":BarChartComponent}]
   loadComponent(){ 
        for(var i=0;i<data.length;i++){
         const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponentForCardType(data[i].name));

         let viewContainerRef = this.componentBind.viewContainerRef;

         const componentRef = viewContainerRef.createComponent(componentFactory);
         (<ImportcomponentComponent>componentRef.instance).datasource = this.datasourceData;
        }
}
Related