I'm building a data table component that is being designed as very generic component.
The idea is to define the table as this:
<app-datatable [items]="currentPageResult">
<app-datatable-column attribute="id"
header="ID"></app-datatable-column>
<app-datatable-column attribute="name"
header="First Name"></app-datatable-column>
<app-datatable-column attribute="last_name"
header="Last Name"></app-datatable-column>
</app-datatable>
In this way, we can specify the array into the datatable component, and by defining datatable-columns we can decide witch attributes should display the table. Internally the table will do an ngFor by columns and another ngFor by items.
This part was simple and now it's working very well, the thing becomes tricky when I want custom html content into the td; Something like this:
<app-datatable [items]="currentPageResult">
<app-datatable-column attribute="id"
header="ID"
[template]="titleTemplate">
<ng-template #titleTemplate>
<a role="button" [routerLink]="[data.id]">
{{data.id}}
</a>
</ng-template>
</app-datatable-column>
<app-datatable-column attribute="name"
header="First Name"></app-datatable-column>
<app-datatable-column attribute="last_name"
header="Last Name"></app-datatable-column>
</app-datatable>
Notice that i'm using data
as the iteration variable, but it actually doesn't work, I'm just illustrating what I want to do.
To solve this, I've used a '$data' as asimple string and a custom directive to replace '$data' with the corresponding column/row value.
In the datatable component I'm using a appDatatableContent
directive on each tbody td
passig the row
data and the colum
settings (the directive only applies if the colum settings has a template):
<table class="table">
<thead>
<tr>
<th *ngFor="let col of columns" [ngClass]="getColumnHeaderClasses(col)" (click)="onReorder(col)">{{col.header}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of items.Items">
<td *ngFor="let col of columns" appDatatableContent [column]="col" [row]="row">
<ng-container *ngIf="!col.template; else col.template">
{{row[col.attribute]}}
</ng-container>
</td>
</tr>
</tbody>
</table>
And the directive basically looks for an element that contains '$data' inside and replaces '$sdata' for the corresponding column value as this:
ngAfterViewInit() {
if (!this.column.template) {
return;
}
const element: HTMLElement = this.element.nativeElement;
const value = this.row[this.column.attribute];
const children = element.getElementsByTagName('*');
const length = children.length;
for (let i = 0; i < length; i++) {
const currentNode = children[i];
if (!currentNode.children || !currentNode.children.length) {
const originalHTML: string = currentNode.innerHTML;
const fixedHTML = originalHTML.replace('$data', value);
currentNode.innerHTML = fixedHTML;
}
}
}
Also, notice that each cell has <ng-container *ngIf="!col.template; else col.template">
so if there is any template binded, the cell content will render that template, but the problem is how to pass arguments (specifically the row object) to the template so the template can use interpolation with that parameter.
See working plunker: https://plnkr.co/edit/84jhiquT5q3OQaCxTa5i
But It doesn't seems to be the best approach, because i can not get advantage of a lot o angular power since I'm just replacing a string value.
So, ¿How can I define a dynamic template that can use a iteration variable to render custom cell contents?
You can easily solve your problem by using built-in directive NgTemplateOutlet
which allows you to pass context to EmbeddedView
(ng-template
)
First remove DatatableContentDirective
Then change markup in
data-table.component.html
<td *ngFor="let col of columns">
<ng-container *ngIf="!col.template; else customTemplate">
{{row[col.attribute]}}
</ng-container>
<ng-template #customTemplate
[ngTemplateOutlet]="col.template"
[ngTemplateOutletContext]="{ col: col, row: row }">
</ng-template>
</td>
and use in parent component
<ng-template #titleTemplate let-col="col" let-row="row">
<a role="button" ...>
<b>Custom</b> {{row[col.attribute]}}
</a>
</ng-template>
See also more information about template variable let-name