6

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?

3
  • I dont know the shape of your data but can't you just use *ngFor again? Commented Jan 16, 2018 at 21:19
  • 2
    Tbody can be repeated also. A table is not limited on tbody count Commented Jan 16, 2018 at 21:23
  • @charlietfl I didn't know that! That's exactly what I needed. Thank you! Could you add it as an answer so I can accept it? Commented Jan 16, 2018 at 21:36

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

0

You could use a 2 dimensional array: Array<Array<number>> where the 1st dimension is the quantity of data-rows, the second is the number of <tr> to render.

For example:

<data-row *ngFor="let row of array2d" [rowQuantity]="row" ></data-row>

and inside your data-row component:

<tr *ngFor="let tr of rowQuantity"></tr>

6 Comments

you can use ng-container (Angular doesn't put it in the DOM) see angular.io/guide/…
Thank you, but the number of rows the component renders is dynamic, i.e. changes after it's instantiated after user interaction. The parent has no way of knowing in advance how many rows there will be, and should be agnostic about it.
@Eliseo I'll try ng-container. It's currently working with multiple TBODY elements as suggested by charlietfl above. Ng-container sounds lighter.
@GuillermoPrandi "The parent has no way of knowing in advance how many rows there will be" - Angular is databound so why does this matter (the table doesn't know how many <tbody>'s it's about to have either)? as soon as the user instantiates a new one, update the array which will trigger ngOnChanges and then the view will update to display the new rows.
@Zze The parent must not know how many rows there are per children for modularity purposes. I cannot move the expansion logic to the parent because that would mean that the parent messes with the internal structure of the child. Besides, I'd rather not redraw the whole table each time (I plan to use an animation for the appearing/dissapearing rows).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.