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?
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With