11
votes

I'm creating a reusable datatable using ngx-datatable and I would like to have dynamic components rendered inside the row detail. The datatable component receives a component class as an argument from a parent module and I use ComponentFactory to createComponent. I can see that the constructor and the onInit methods are running for the dynamic component but it is not being attached to the DOM.

This is what the datatable html looks like for the row-detail :

 <!-- [Row Detail Template] -->
        <ngx-datatable-row-detail rowHeight="100" #myDetailRow (toggle)="onDetailToggle($event)">
          <ng-template let-row="row" #dynamicPlaceholder let-expanded="expanded" ngx-datatable-row-detail-template>
          </ng-template>
        </ngx-datatable-row-detail>
 <!-- [/Row Detail Template] -->

And this is what my .ts file looks like :

@ViewChild('myDetailRow', {static: true, read: ViewContainerRef}) myDetailRow: ViewContainerRef;
@ViewChild('dynamicPlaceholder', {static: true, read: ViewContainerRef}) dynamicPlaceholder: ViewContainerRef;

renderDynamicComponent(component) {
        var componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
        var hostViewConRef1 = this.myDetailRow;
        var hostViewConRef2 = this.dynamicPlaceholder;
        hostViewConRef1.createComponent(componentFactory);
        hostViewConRef2.createComponent(componentFactory);
}

Another point is that if my #dynamicPlaceholder ng-template is placed outside of ngx-datatable, it works and the dynamic module is rendered and displayed.

2
This is to do with content projection. Anything placed inbetween a component tag, is not rendered unless the component has a <ng-content> tag to render any nested markup.C_Ogoo
Could you please help me with a quick example to help in my case?Raghav Kanwal

2 Answers

5
votes

We can't render a component into a Template (<ng-template>) at runtime with createComponent
because afaik templates get processed by Angular at compile time. So we need a solution that works at compile time.


Solution with drawbacks

ng-content can help us here:

<!-- [Row Detail Template] -->
<ngx-datatable-row-detail rowHeight="100" (toggle)="onDetailToggle($event)">
   <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template>
      <ng-content></ng-content>
   </ng-template>
</ngx-datatable-row-detail>
<!-- [/Row Detail Template] -->

We can then pass anything we want to the detail view:

<my-table>From the ouside but I cant access the current row :(</my-table>

But there is a problem: We can't use ng-content when we want to access the current row in the passed template.


Solution

But ngx-datatable got us covered. We can pass a template to thengx-datatable-row-detail directive:

<ngx-datatable-row-detail [template]="myDetailTemplate "rowHeight="100" (toggle)="onDetailToggle($event)">
</ngx-datatable-row-detail>

The template can then be passed into from any component on the outside via a @Input variable:

<ng-template #myDetailTemplate let-row="row">
  From the outside with access to the current row: {{row.name}}
</ng-template>

Take a look at the stackblitz, where I wrote a my-table component as poc.

0
votes

Define a component that exposes its content as TemplateRef

<ng-template #myTemplate let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template>

    <div><strong>Address</strong></div>
    <div>{{ row?.address?.city }}, {{ row?.address?.state }}</div>
</ng-template>

Use ViewChild to make an accessible property for TemplateRef

export class DynamicComponent implements OnInit {

  @ViewChild("myTemplate",{static:true}) myTempalte : TemplateRef<any>
  ...
}

Define your row detail without template

<ngx-datatable-row-detail rowHeight="100" (toggle)="onDetailToggle($event)">
</ngx-datatable-row-detail>

Define a property to access the directive

@ViewChild(DatatableRowDetailDirective,{static:true}) templ:DatatableRowDetailDirective; 
constructor(  
    private cfr: ComponentFactoryResolver, //import cfr
  )
....
toggleExpandRow(row) { 
   const factory = this.cfr.resolveComponentFactory<DynamicComponent>(
      DynamicComponent
    );
  
    const component = factory.create(this.injector,[]);   //create component dynamically
    this.templ._templateInput = component.instance.myTempalte; // set directives template to your components template 
    this.table.rowDetail.toggleExpandRow(row);
}

Stackblitz

Edit: I fiddled with ngx-datatable source sad part of it ngx-datatable-row-detail is not a component but directive and it isn't attached to DOM. So it has no ViewContainer ref. This makes injecting elements into it a little bit hard. What you can do is define templates in your component and use TemplateRefs and assign them where you render your component.