4

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(); } 
2
  • Had a very similar issue while implementing a DataGrid component. I wanted to support dynamic component injection to column cells and stumbled on this Angular error... You've put me on the right track, calling .detectChanges() on the newly instantiated component (after setting all input/output property values) did the trick! viewContainerRef.createComponent(componentFactory).changeDetectorRef.detectChanges() Commented Jun 6, 2018 at 16:26
  • The full code is there for my use case :) github.com/knlambert/the-toolbox/blob/master/client/src/app/… (a grid of embedded components) Commented Jun 6, 2018 at 16:30

3 Answers 3

0

Try this:

constructor(private cd: ChangeDetectorRef) { } ngAfterViewInit() { ...... this.cd.detectChanges(); } 
Sign up to request clarification or add additional context in comments.

2 Comments

Doesn't work, but I don't have the error anymore If I put it just before target.createComponent(widgetComponent); Seems a little hacky.
After not before.
0

You could use a timeout.

setTimeout( this.widgetTargets.changes.subscribe(() => { this.refresh() }) ,0); 

3 Comments

setTimeout will trigger change detection for entire application
Can you provide me with any source of that?
Just try it. seTimeout as macrotask will run app.tick
0

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; } } 

Comments