Jonathan Kuhl
Jonathan Kuhl

Reputation: 709

Generating Two Table Rows With *ngFor

I am working on an application to display the results of testing a website with Java's TestNG. I know it's a bit redundant since TestNG already generates its own site, but it's part of a training exercise for a bootcamp.

I've come upon a peculiar issue while generating a table with *ngFor. The issue is that if a test comes back failed, it comes back with a stack trace. Because Java stack traces are so verbose, that stack trace is placed on a new row, therefore I cannot put the *ngFor on the <tr> tag as one would normally do. I was hesitant to put it on the <tbody> tag because I feared that that would cause layout issues (and it does) so I tried wrapping the two <tr> tags in a <div> and putting the *ngFor on that. That did not work, because now my table headers weren't aligned with the table's content. I ended up putting the *ngFor directive on the <tbody> as that seemed mostly all right, even though it produced some artifacts between my rows as if they have borders.

However I still don't like that approach. Not only is it hacky as it is, it creates some bugs on very large layouts.

<table class="table table-striped table-dark table-hover table-sm table-responsive" *ngIf="loaded">
<thead>
    <tr>
        <th>Name</th>
        <th>Signature</th>
        <th>Duration</th>
        <th>Started At</th>
        <th>Finished At</th>
        <th>Status</th>
    </tr>
</thead>
<tbody *ngFor="let method of testResults.tests; let index = index">
        <tr>
            <td>{{ method.name | camelToTitle }}</td>
            <td>{{ method.signature }}</td>
            <td>{{ method.duration_ms }} ms</td>
            <td>{{ method.startTime | dates }}</td>
            <td>{{ method.finishTime | dates }}</td>
            <td
                [ngStyle]="{ background: selectColor(method.status) }">
                {{ method.status |passfail }}
            </td>
        </tr>
        <tr>
            <td *ngIf="method.exceptionClass != null" class="table-danger">{{ method.exceptionClass }}</td>
            <td *ngIf="method.exceptionClass != null" colspan="5" class="table-danger">
                <button class="btn btn-dark" (click)="toggleStackTrace(index)">{{ btnText }} Stack Trace</button>
                <span class="font-weight-bold ml-1">Exception Message: {{ method.exceptionMessage }}</span>
                <div *ngIf="method.showStackTrace">
                    <p [appStacktrace]="method.stackTrace"></p>
                </div>
            </td>
        </tr>
</tbody>
</table>

Essentially what I'm trying to do is generate a row for each test that gets run, and, if the test has a stacktrace, have a row for that as well. Is there a good, clean way to go about doing this?

Upvotes: 4

Views: 2442

Answers (1)

user184994
user184994

Reputation: 18271

The best way to do this would be to use ng-container.

The Angular docs describe ng-container as

a grouping element that doesn't interfere with styles or layout because Angular doesn't put it in the DOM.

read more here

This allows you to wrap your HTML in an ng-container tag, like so:

<table class="table table-striped table-dark table-hover table-sm table-responsive" *ngIf="loaded">
<thead>
    <tr>
        <th>Name</th>
        <th>Signature</th>
        <th>Duration</th>
        <th>Started At</th>
        <th>Finished At</th>
        <th>Status</th>
    </tr>
</thead>
<tbody>
    <ng-container *ngFor="let method of testResults.tests; let index = index">
        <tr>
            <td>{{ method.name | camelToTitle }}</td>
            <td>{{ method.signature }}</td>
            <td>{{ method.duration_ms }} ms</td>
            <td>{{ method.startTime | dates }}</td>
            <td>{{ method.finishTime | dates }}</td>
            <td
                [ngStyle]="{ background: selectColor(method.status) }">
                {{ method.status |passfail }}
            </td>
        </tr>
        <tr>
            <td *ngIf="method.exceptionClass != null" class="table-danger">{{ method.exceptionClass }}</td>
            <td *ngIf="method.exceptionClass != null" colspan="5" class="table-danger">
                <button class="btn btn-dark" (click)="toggleStackTrace(index)">{{ btnText }} Stack Trace</button>
                <span class="font-weight-bold ml-1">Exception Message: {{ method.exceptionMessage }}</span>
                <div *ngIf="method.showStackTrace">
                    <p [appStacktrace]="method.stackTrace"></p>
                </div>
            </td>
        </tr>
    </ng-container>
</tbody>
</table>

The behavior of *ngFor is maintained, but the DOM will only ever contain the expected tags (tbody, tr, td, etc)

Upvotes: 8

Related Questions