Search code examples
angulardynamiccomponents

Render dynamic created components inside a specific DOM element


I have a parent component as below

<div class="container-fluid p-0">
  <div class="row">
    <app-tile-render #tileRender [itemMasterId]="itemMasterId" [partName]="partName" [conditionIds]="conditionIds"
      [showViewIcon]="true"></app-tile-render>
  </div>
</div>

Below is the renderer component where I am using ng-template to render the dynamically generated components

@Component({
    selector: 'app-tile-render',
    template: `<ng-template tileHost>
    </ng-template>`
})
export class TileRenderComponent implements OnInit {
    @Input() tiles: TileItem[] = [];
    @Input() itemMasterId: number;
    @Input() partName: string;
    @Input() conditionIds: any;
    @Input() showViewIcon: boolean;

    @ViewChild(TileDirective, { static: false }) tileHost!: TileDirective;

    constructor(public tileService: TileService,
        public componentFactoryResolver: ComponentFactoryResolver,
        private renderer: Renderer2) { }

    ngOnInit() {
    }

    loadComponent() {
        this.tiles = this.tileService.getTiles();

        const viewContainerRef = this.tileHost.viewContainerRef;
        viewContainerRef.clear();

        for (let i = 0; i < this.tiles.length; i++) {
            const tileItem = this.tiles[i];
            const componentFactory = this.componentFactoryResolver.resolveComponentFactory(tileItem.component);

            const componentRef = viewContainerRef.createComponent<TileComponent>(componentFactory);
            componentRef.instance.itemMasterId = this.itemMasterId;
            componentRef.instance.partName = this.partName;
            componentRef.instance.conditionIds = this.conditionIds;
            componentRef.instance.showViewIcon = this.showViewIcon;
        }
    }
}

tile.service.ts

@Injectable()
export class TileService {
    @Input() itemMasterId: any;
    getTiles() {
        return [
            new TileItem(
                CommonPurchaseOrderListComponent
            ),
            new TileItem(
                CommonRepairOrderListComponent
            ), new TileItem(
                CommonSalesOrderListComponent
            )
        ];
    }
}

But here the issue is that it renders all the components (tiles) at once as below:

enter image description here

Instead of that I want to render all the component inside each bootstrap column so that it looks in a proper sequence.

like

<div class="row">
    <div class="col-md-4">
        want to display each dynamic generated component here..............
    </div>
</div>

Solution

  • In loadComponent function you can initialize a new constant, which will be holding the columns:

    const rowDiv = this.renderer.createElement('div');
    this.renderer.addClass(rowDiv, 'row');
    

    Then, inside a for loop, after You initialize constant componentRef You should trigger a new function, which will hold a reference to the rowDiv and also a componentRef, to later on nest it inside wrapping <div class="col-md-4"></div>

    const componentRef = viewContainerRef.createComponent<TileComponent>(componentFactory);
    this.createColumnForComponent(rowDiv, componentRef);
    

    Function createColumnForComponent:

      private createColumnForComponent(
        componentRef: ComponentRef<any>,
        rowDiv: HTMLElement
      ) {
        // setup a div with class col-md-4
        const columnDiv: HTMLElement = this.renderer.createElement('div');
        this.renderer.addClass(columnDiv, 'col-md-4');
        const componentElement: HTMLElement = componentRef.location.nativeElement;
        // append component to column
        this.renderer.appendChild(columnDiv, componentElement);
        // append column to row
        this.renderer.appendChild(rowDiv, columnDiv);
      }
    

    After that function our rowDiv is filled with the columns. We need to make use of it, so we trigger it once, after the for loop execution:

    for (let i = 0; i < this.tiles.length; i++) {
    ...
    }
    this.createRowForColumns(viewContainerRef, rowDiv);
    

    Function createRowForColumns:

      private createRowForColumns(
        viewContainerRef: ViewContainerRef,
        rowDiv: HTMLElement
      ) {
        // append row to our selector
        this.renderer.appendChild(
          viewContainerRef.element.nativeElement.parentNode,
          rowDiv
        );
    

    In following code I made a use of renderer but You could also follow more native approach, like:

    columnDiv.appendChild(componentElement);
    columnDiv.classList.add('col-md-4');