Reputation: 155
I am writing an HTML table component using data that is nested, such that the output might look like this:
<table>
<tr><td>Parent</td></tr>
<tr><td>Child 1</td></tr>
<tr><td>Child 2</td></tr>
<tr><td>Grandchild 1</td></tr>
</table>
I would like to create this using a recursive component as follows:
<table>
<data-row *ngFor="let row of rows" [row]="row"></data-row>
</table>
data-row:
<tr><td>{{row.name}}</td></tr>
<data-row *ngFor="let child of row.children" [row]="child"></data-row>
However, this adds a wrapping element around the table row which breaks the table and is invalid HTML:
<table>
<data-row>
<tr>...</tr>
<data-row><tr>...</tr></data-row>
</data-row>
</table>
Is it possible to remove this data-row
wrapping element?
One Solution:
One solution is to use <tbody data-row...></tbody>
which is what I'm currently doing, however this leads to nested tbody
elements which is against the W3C spec
Other thoughts:
I've tried using ng-container
but it doesn't seem to be possible to do <ng-container data-row...></ng-container>
so that was a non starter.
I have also considered ditching the use of tables, however using an HTML table is the ONLY way to allow simple copying of the data into a spreadsheet which is a requirement.
The final option I've considered would be to flatten the data before generating the table, however, since the tree can be expanded and collapsed at will, this leads to either excessive rerendering or a very complicated model.
EDIT: Here's a Plunk with my current solution (which is against spec): http://plnkr.co/edit/LTex8GW4jfcH38D7RB4V?p=preview
Upvotes: 5
Views: 3200
Reputation: 361
I found a solution from another stackoverflow thread, so I can't take credit, but the following solution worked for me.
Put :host { display: contents; }
into the data-row
component .css file.
Upvotes: 1
Reputation: 3062
posting another answer just to show what i was talking about ... I'll leave you alone after this, promise. Heh.
http://plnkr.co/edit/XcmEPd71m2w841oiL0CF?p=preview
This example renders everything as a flat structure, but retains the nested relationships. Each item has a reference to its parent and an array of its children.
import {Component, NgModule, VERSION, Input} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
@Component({
selector: 'my-app',
template: `
<table *ngIf="tableReady">
<tr *ngFor="let row of flatList" data-table-row [row]="row"> </tr>
</table>
`,
})
export class App {
tableReady = false;
table = [
{
name: 'Parent',
age: 70,
children: [
{
name: 'Child 1',
age: 40,
children: []
},
{
name: 'Child 2',
age: 30,
children: [
{
name: 'Grandchild 1',
age: 10,
children: []
}
]
}
]
}
];
flatList = [];
ngOnInit() {
let flatten = (level : any[], parent? :any ) => {
for (let item of level){
if (parent) {
item['parent'] = parent;
}
this.flatList.push(item);
if (item.children) {
flatten(item.children, item);
}
}
}
flatten(this.table);
this.tableReady = true;
}
}
@Component({
selector: '[data-table-row]',
template: `
<td>{{row.name}}</td><td>{{row.age}}</td>
`
})
export class DataTableRowComponent {
@Input() row: any;
}
Upvotes: 0
Reputation: 3062
Just use a class or attribute as the selector for the component and apply it to a table row element.
@Component({
selector: [data-row],
with
<tr data-row> </tr>
or
@Component({
selector: .data-row,
with
<tr class="data-row"></tr>
EDIT - i can only get it to work using content projection in the child component, and then including the td elements inside the components element in the parent. See here - https://plnkr.co/edit/CDU3Gn1Fg1sWLtrLCfxw?p=preview
If you do it this way, you could query for all the rows by using ContentChildren
import { Component, ContentChildren, QueryList } from '@angular/core';
import { DataRowComponent } from './wherever';
somewhere in your component...
@ContentChildren(DataRowComponent) rows: QueryList<DataRowComponent>;
That will be defined in ngAfterContentInit
ngAfterContentInit() {
console.log(this.rows); <-- will print all the data from each component
}
Note - you can also have components that recurse (is that a word?) themselves in their own templates. In the template of data-row component, you have any number of data-row components.
Upvotes: 1
Reputation: 28708
If you wrap row components in a ng-container you should be able to get it done
<tbody>
<ng-container *ngFor="let row of rows; let i = index">
<data-row [row]="row"></data-row>
</ng-container>
</tbody>
@Component({
selector: 'my-app',
template: `
<table>
<ng-container *ngFor="let row of table">
<tbody data-table-row [row]="row"></tbody>
</ng-container>
</table>
`,
})
export class App {
table = [
{
name: 'Parent',
children: [
{
name: 'Child 1'
children: []
},
{
name: 'Child 2'
children: [
{
name: 'Grandchild 1'
children: []
}
]
}
]
}
]
}
@Component({
selector: 'tbody[data-table-row]',
template: `
<tr><td>{{row.name}}</td></tr>
<tbody *ngFor="let child of row.children" data-table-row [row]="child"></tbody>
`
})
export class DataTableRowComponent {
@Input() row: any;
}
Upvotes: 0