Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple <tr> in an Angular row template

I know a similar question to this is all over the site, but my problem is slightly different from those.

I want to write a component that serves rows, but each instance may generate from one to many rows, depending on the provided data:

<tbody>
    <!-- intended usage -->
    <data-rows *ngFor="let row of dataRows" [row]="row">
    </data-rows>
</tbody>

Needs to create:

<tbody>

    <!-- from first <data-rows> instance -->
    <tr><td> instance 1, row 1 </td></tr>

    <!-- from second <data-rows> instance -->
    <tr><td> instance 2, row 1 </td></tr>
    <tr><td> instance 2, row 2 </td></tr>
    <tr><td> instance 2, row 3 </td></tr>

    <!-- from third <data-rows> instance -->
    <tr><td> instance 3, row 1 </td></tr>

</tbody>

Most solutions propose using an attribute selector and use *ngFor on a real <tr>. This will not work in my case because there's not a one-to-one relationship between instances and rows. In addition, the parent component doesn't know how many <tr> should be rendered; that's for <data-rows> to decide.

Of course a naive implementation of <data-rows> would fail, as it would add unsupported elements to the tbody:

<tbody>

    <data-rows>
        <!-- from first <data-rows> instance -->
        <tr><td> instance 1, row 1 </td></tr>
    </data-rows>

    <data-rows>
        <!-- from second <data-rows> instance -->
        <tr><td> instance 2, row 1 </td></tr>
        <tr><td> instance 2, row 2 </td></tr>
        <tr><td> instance 2, row 3 </td></tr>
    </data-rows>

    <data-rows>
        <!-- from third <data-rows> instance -->
        <tr><td> instance 3, row 1 </td></tr>
    </data-rows>

</tbody>

That doesn't work because tbody can only contain <tr> elements, so the table logic breaks.

My intuition (as that of many who asked a similar question) is to render <data-rows> without the actual <data-rows> element (only its contents) but I think it might not be supported because the css emulate mode would break.

What's a good way of solving this without breaking the initial premise?

like image 877
Guillermo Prandi Avatar asked Oct 28 '22 20:10

Guillermo Prandi


1 Answers

As pointed out by user charlietfl in the comments, I can use multiple TBODY tags (never occurred to me), so in my case I can easily generate:

<tbody data-rows class="instance-1">
    <tr><td> instance 1, row 1 </td></tr>
</tbody>

<tbody data-rows class="instance-2">
    <tr><td> instance 2, row 1 </td></tr>
    <tr><td> instance 2, row 2 </td></tr>
    <tr><td> instance 2, row 3 </td></tr>
</tbody>

<tbody data-rows class="instance-3">
    <tr><td> instance 3, row 1 </td></tr>
</tbody>

In my component, the selector is tbody[data-rows], and I can have a parent template like this:

<table>
    <thead>
        <tr><th>Table header</th></tr>
    </thead>
    <tbody data-rows *ngFor="let row of data" [row]="row"></tbody>
</table>

This method has the additional advantage of allowing an even/odd styling on an instance (tbody) basis, which in my case makes sense.

The row generator's template is simply another ngIf/ngFor driven template as needed:

<tr><td>This row will always be shown</td></tr>
<tr *ngIf="..."><td>This row, only if first expansion is needed</td></tr>
<tr *ngIf="..."><td>This row, only if second expansion is needed</td></tr>
<tr *ngFor="..."><td>More additional rows</td></tr>

etc.

like image 164
Guillermo Prandi Avatar answered Oct 31 '22 22:10

Guillermo Prandi