Dynamically adding components in ngFor

If you have this pattern:

<div *ngFor="for item in items">
  <!-- needs to be one per item -->
  <ng-template details-directive></ng-template>
</div>

I suggest wrapping the directive in a component:

@Component({
  selector: 'details-wrapper',
  template: '<ng-template details-directive></ng-template>'
})
export class DetailsWrapper {
  @Input item?: Item;
  // Dynamically load details using the regular solution.
}

And making this your for loop:

<div *ngFor="for item in items">
  <details-wrapper [item]="item"></details-wrapper>
</div>

After some research, this is the solution i came up with (works in angular 4.0.0).

Load all the ViewContainerRef targets by id:

@ViewChildren('dynamic', {read: ViewContainerRef}) public widgetTargets: QueryList<ViewContainerRef>;

Then loop over them to get the target, create a factory for the component and call createComponent.
Also can use the component reference to subscribe or set other component properties.

ngAfterViewInit() {
    const dashWidgetsConf = this.widgetConfigs();
    const widgetComponents = this.widgetComponents();
    for (let i = 0; i < this.widgetTargets.toArray().length; i++) {
        let conf = dashWidgetsConf[i];
        let component = widgetComponents[conf.id];
        if(component) {
            let target = this.widgetTargets.toArray()[i];
            let widgetComponent = this.componentFactoryResolver.resolveComponentFactory(component);
            let cmpRef: any = target.createComponent(widgetComponent);

            if (cmpRef.instance.hasOwnProperty('title')) {
                cmpRef.instance.title = conf.title;
            }
        }
    }
}

The widgetComponents is a object {key: component} and widgetConfigs is where i store specific component info - like title, component id etc.

Then in template:

<div *ngFor="let box of boxes; let i = index" >
    <ng-template #dynamic></ng-template>
</div>

And the order of targets is the same as in my conf ( boxes is generated from it) - which is why i can loop through them in order and use i as index to get the correct conf and component.


After

if (cmpRef.instance.hasOwnProperty('title')) {
   cmpRef.instance.title = conf.title;
}

You can add this.cd.detectChanges(); or you will have the error "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked" in your children component with Angular 6x.

Tags:

Angular