I am creating a datatable module and I am aiming for a particular implementation. I want to be able to import the module, and use it's components like so:
randomcomponent.component.html
<datatable [data]="tableData">
<datatable-column>
<ng-template let-row="row">
<label> {{ row.value }} </label>
</ng-template>
</datatable-column>
</datatable>
Here are the components from the datatable module:
datatable.component.html (<datatable>)
<table class="datatable">
<thead>
<tr>
<th *ngFor="let header of tableData?.headers">
{{ header?.title }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of tableData?.data">
<ng-container *ngTemplateOutlet="template; context: { row: row }"></ng-container>
</tr>
</tbody>
<tfoot>
</tfoot>
</table>
column.component.html (<datatable-column>)
<ng-template let-row="row">
<ng-container *ngTemplateOutlet="template; context: { row: row }"></ng-container>
</ng-template>
The data isn't rendering though. I had used a previous approach with ng-content
but was not able to loop through content. Only 1 displayed. How can I end up with the implementation I am looking for? Should I seek a different approach?
UPDATE: Provided is my StackBlitz https://stackblitz.com/edit/angular-4esuor
The key here is that you need to find the nested template inside the outer templates, and then iterate those outer templates.
You could enlist the help of another directive here (recommended but not mandatory, see NOTE below), let's call it the datatable-cell
directive, it's very simple, declare and export it:
@Directive({
selector: '[datatable-cell]'
})
export class DatatableCellDirective {
constructor(public templateRef: TemplateRef<any>) { }
}
Just exposes a template ref, does nothing else.
Next, if using the directive, you add it inside all your columns to the templates (and just declare let-row
if using $implicit
context... or stick to let-row="row"
if you want explicit context):
<datatable-column>
<ng-template datatable-cell let-row>
<label> {{ row.value }} </label>
</ng-template>
</datatable-column>
In your column component, make use of that directive to find and expose the cell template with ContentChild
:
@ContentChild(DatatableCellDirective)
cell: DatatableCellDirective
NOTE: You could do this without the DatatableCellDirective
, and just set @ContentChild(TemplateRef) cell: TemplateRef<any>
in the column, but this is a bit less flexible, and I recommend using the directive (explained below).
In your table component, find the columns with ContentChildren
as there are many, and get their cell templates:
@ContentChildren(DatatableColumnComponent)
columns: QueryList<DatatableColumnComponent>;
cellTemplates: TemplateRef<any>[] = []
// content children available in this hook
ngAfterContentInit() {
this.cellTemplates = this.columns.toArray().map(c => c.cell.templateRef);
// like this if not using the cell directive and using TemplateRef directly
// this.cellTemplates = this.columns.toArray().map(c => c.cell);
}
Then, in your table html, iterate both the data rows AND cell templates, as you need a cell rendered for each column in each row, setting context to $implicit
here, but use whatever context is appropriate, (such as {row: row}
instead for explicit context):
<tr *ngFor="let row of tableData?.data">
<ng-container *ngFor="let tpl of cellTemplates">
<ng-container *ngTemplateOutlet="tpl; context: {$implicit: row}"></ng-container>
</ng-container>
</tr>
You could also put <td></td>
tags inside the column iteration to have actual table cells that enforce column widths.
This is a fairly flexible system... you could envision extending it so you can provide custom column headers in the columns, along with a datatable-header
directive to facilitate, and you could find those templates in your column and table components similar to how cells are found, in order to iterate and display those in your header row. Using the directive also allows you to use things other than ng-template
to build the template (like a td
tag), and can potentially allow you to add more context to your cells via inputs.
Blitz: https://stackblitz.com/edit/angular-gj5ean
Minor edit, realized that ContentChildren
are iterable, so you can actually skip the ngAfterContentInit
part in the table component, doing:
@ContentChildren(DatatableColumnComponent)
columns: QueryList<DatatableColumnComponent>;
And just do this in template...
<ng-container *ngFor="let col of columns">
<ng-container *ngTemplateOutlet="col.cell.templateRef; context: {$implicit: row}"></ng-container>
</ng-container>
Which provides better change detection in the event of the columns themselves changing (eg adding / removing columns)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.