Angular populating table with object data that has missing fields - html

i'm currently working on displaying large amounts of data in tables for a scientific project. i have timeStamps of experiments, which look like this:
interface TimeData {
time: string;
data: {SD: string, SEM: string, N: string, MEAN: string};
}
i have a total of 11 timeStamps in the table, all described by their 'time', e.g. 15, 30, 60, 90...
if an experiment is missing a timestamp completely or has no data for the timestamp 30 at e.g. MEAN, i want to print -- in the td.
i can't seem to wrap my head around how to display all mean data in a row and just replacing missing one's with a '--'...
i've tried wrapping in ng-container, several ngFor loops, yet it always comes out wrong, i.e. i get too many -- or none at all and my data is being displayed in the wrong td.
here's the html
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">Code</th>
<th scope="col">Function</th>
<th scope="col" class="text-center"
*ngFor="let timeStamp of maxTimeStamps; let i=index">t{{i + 1}}
{{timeStamp.time}}</th>
<th>ΔAUC</th>
</tr>
</thead>
<tbody *ngFor="let experiment of report.experiments; let i = index">
<tr>
<td rowspan="4" [ngStyle]="{color: experimentColors[i]}">{{experiment.code.toUpperCase()}}</td>
<td>mean</td>
------missing td goes here-------
----------------------------------
</tr>
</tbody>
</table>

Welcome to StackOverflow, If I get it right you want to show your data in rows but you add ngFor in the wrong place for that (it shouldn't add to tbody tag)! please check the corrected code below.
You can change or make a condition by ngIf if you don't want to add that td.
Good Luck.
<table class="table table-bordered">
<thead>
<tr>
<th scope="col">Code</th>
<th scope="col">Function</th>
<th scope="col" class="text-center"
*ngFor="let timeStamp of maxTimeStamps; let i=index">t{{i + 1}}
{{timeStamp.time}}</th>
<th>ΔAUC</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let experiment of report.experiments; let i = index">
<td rowspan="4" [ngStyle]="{color: experiment.Colors[i]}" [ngIf]="experiment.CheckableItem == true">{{experiment.code.toUpperCase()}}</td>
<td>mean</td>
</tr>
</tbody>
</table>

If some of the properties will not be available in the interface then they should be defined optional. Notice the question mark.
interface TimeData {
time?: string;
data?: {SD?: string, SEM?: string, N?: string, MEAN?: string};
}
In the template, you could use *ngIf directive with an else clause to show '--' if some properties are not available. Try the following
Controller
import { Component, VERSION, ElementRef } from '#angular/core';
import { Router } from '#angular/router';
interface TimeData {
time?: string;
data?: {SD?: string, SEM?: string, N?: string, MEAN?: string};
}
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
timeData: TimeData[] = [
{time: '15', data: {SD: '10', SEM: '20', N: '30', MEAN: '75'}},
{data: {SD: '10', SEM: '20', N: '30', MEAN: '75'}},
{time: '15', data: {SD: '10', SEM: '20', N: '30'}},
{data: {SEM: '20', N: '30', MEAN: '75'}},
];
constructor(private _router: Router, private _el: ElementRef) { }
}
Template
<table>
<tr>
<th>Time</th>
<th>SD</th>
<th>SEM</th>
<th>N</th>
<th>MEAN</th>
</tr>
<tr *ngFor="let data of timeData">
<ng-container *ngIf="data.time; else emptyData">
<td>{{ data.time }}</td>
</ng-container>
<ng-container *ngIf="data.data.SD; else emptyData">
<td>{{ data.data.SD }}</td>
</ng-container>
<ng-container *ngIf="data.data.SEM; else emptyData">
<td>{{ data.data.SEM }}</td>
</ng-container>
<ng-container *ngIf="data.data.N; else emptyData">
<td>{{ data.data.N }}</td>
</ng-container>
<ng-container *ngIf="data.data.MEAN; else emptyData">
<td>{{ data.data.MEAN }}</td>
</ng-container>
<ng-template #emptyData>
<td>--</td>
</ng-template>
</tr>
</table>
Working example: Stackblitz

Related

Display multi arrays with rowspan in angular

Hi I am trying to create a table with rowspan based on array lengths. It works for me with 2 nested arrays but I have no idea ho to do it with more nest levels.
For data:
data = [
{ id: 1, name: 'one', groups:
[{'name': 'gr1', campaigns :
[{'name' : 'camp1'}, {'name' : 'camp2'}, {'name' : 'camp3'}]},
{'name': 'gr2', campaigns :
[{'name' : 'camp4'}, {'name' : 'camp5'}, {'name' : 'camp6'}]}
] },
{ id: 2, name: 'two', groups: [{'name': 'gr3', campaigns : [{'name' : 'camp7'}]}] },
{ id: 3, name: 'three', groups: [{'name': 'gr4', campaigns : [{'name' : 'camp8'}]}] },
{ id: 4, name: 'four', groups: [{'name': 'gr5', campaigns : [{'name' : 'camp9'}]}] }
];
I tried:
<table class="table table-bordered table-hover hidder" id="report">
<!-- Header -->
<tr>
<th >Project</th>
<th>Group</th>
<th>Camps</th>
</tr>
<ng-container *ngFor="let project of data; let i = index;">
<tr>
<td [attr.rowspanspan]="project.groups.length"> {{project.name}} </td>
<ng-container *ngFor="let group of project.groups; let j = index">
<tr>
<td>
{{ group.name }}
</td>
<!-- <ng-container *ngFor="let campaign of group.campaigns; let k = index">
<tr>
<td >
{{ campaign.name }}
</td>
</ng-container> -->
</tr>
</ng-container>
</ng-container>
</table>
My goal is to get table like this in picture:
image example
For exampleI have code here:
working code example
The nature of the table structure in HTML makes it quite difficult to split it programmatically. You could try the following, but then you'll have to style the table yourself:
<table class="table table-bordered table-hover hidder" id="report">
<!-- Header -->
<tr>
<th>Project</th>
<th>Group</th>
<th>Camps</th>
</tr>
<ng-container *ngFor="let project of data; let i = index">
<tr>
<td>{{ project.name }}</td>
<td>
<table>
<ng-container *ngFor="let group of project.groups; let j = index">
<tr click="test(group);">
<td>{{ group.name }}</td>
</tr>
</ng-container>
</table>
</td>
<td>
<table>
<ng-container *ngFor="let group of project.groups; let j = index">
<ng-container *ngFor="let campaign of group.campaigns; let k = index">
<tr click="test(group);">
<td>{{ campaign.name }}</td>
</tr>
</ng-container>
</ng-container>
</table>
</td>
</tr>
</ng-container>
</table>
Check out the StackBlitz example.
Should be [attr.rowspan], but I think you should remove it, see your forked stackblitz.
NOTE: I added in the .css to override the bootstrap padding of a table
.table > :not(caption) > * > *
{
padding:0 .5rem;
}
NOTE2: we use [attr.rowspan] if we have a "flat" array, like this another SO. not when we have an array with "groups"

How to prevent nz-select from expanding width inside ng-zorro table?

I use nz-select inside the ng-zorro table. Initially, I set the 25% as the width for all column in the table. However, when I select the color, as the name of the color is too long, it expands the color column and it is no longer width 25%.
How can I prevent the color column from expanding and remain as 25% width after I choose the color from the selection dropdown?
I also attached the html and ts file below.
Initially
After choose color
<nz-table #basicTable [nzData]="listOfData">
<thead>
<tr>
<th nzWidth="25%" >Name</th>
<th nzWidth="25%">Age</th>
<th nzWidth="25%">Color <br> (Choose 3)</th>
<th nzWidth="25%">Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of basicTable.data">
<td>{{ data.name }}</td>
<td>{{ data.age }}</td>
<td>
<nz-select style="width: 100%" nzMode="multiple" nzPlaceHolder="Select Colors">
<nz-option *ngFor="let option of colorOption" [nzLabel]="option" [nzValue]="option">
</nz-option>
</nz-select>
</td>
<td>
<a>Delete</a>
</td>
</tr>
</tbody>
</nz-table>
listOfData: Person[] = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park'
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park'
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park'
}
];
colorOption = [
"Option Color Number 1",
"Option Color Number 2",
"Option Color Number 3",
"Option Color Number 4",
"Option Color Number 5",
"Option Color Number 6",
]
<nz-select style="width: 100%" nzMode="multiple" nzPlaceHolder="Select Colors" appSelectWidth>
<nz-option *ngFor="let option of colorOption" [nzLabel]="option" [nzValue]="option">
</nz-option>
</nz-select>
import {AfterViewInit, Directive, ElementRef, OnInit, Renderer2} from '#angular/core';
#Directive({
selector: '[appSelectWidth]'
})
export class SelectWidthDirective implements AfterViewInit {
constructor(private ElementRef: ElementRef, private Render2: Renderer2) {
}
ngAfterViewInit() {
setTimeout(() => {
const {width} = this.ElementRef.nativeElement.getBoundingClientRect();
console.log(width);
this.Render2.setStyle(this.ElementRef.nativeElement, 'width', width + 'px')
}, 200)
}
}
so easy to prevent nz-select from expanding width inside ng-zorro table
you must add nz-select width style in 'px'
like this
<nz-table #basicTable [nzData]="listOfData">
<thead>
<tr>
<th nzWidth="25%" >Name</th>
<th nzWidth="25%">Age</th>
<th nzWidth="25%">Color <br> (Choose 3)</th>
<th nzWidth="25%">Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of basicTable.data">
<td>{{ data.name }}</td>
<td>{{ data.age }}</td>
<td>
<nz-select style="width: 100px" nzMode="multiple" nzPlaceHolder="Select Colors">
<nz-option *ngFor="let option of colorOption" [nzLabel]="option" [nzValue]="option">
</nz-option>
</nz-select>
</td>
<td>
<a>Delete</a>
</td>
</tr>
</tbody>
</nz-table>
check this

Using ngif else to display a different value in table

I am getting data from the database. So in a particular column named card there exists only two types of values. "Debit" or "Credit". But in the backend I only receive values as "D" or "C".
`<div *ngIf="isBank">
<div class="table-responsive">
<table class="table">
<thead class="table_header">
<tr>
<th>Name</th>
<th>Bill</th>
<th>Card</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let bank of bankList; index as i">
<td>{{ bank.name}}</td>
<td>{{ bank.bill}}</td>
<td>{{ bank.card}}</td>
</tr>
</tbody>
</table>`
</div>
</div>
So here in the Card column I get only two values called as 'D' or 'C'. I get these values from the backend. So I want to change the value to 'Debit' if I get 'D' and 'Credit' if I get 'C'. This hads to be done within the template using ngif or whatever works.
You can define an angular pipe
card-type.pipe.ts:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'cardType'})
export class CardTypePipe implements PipeTransform {
transform(value: string): string {
if (value.toLowerCase() === "c")
return "Credit";
else if (value.toLowerCase() === "d")
return "Debit";
else
return value;
}
}
And in the template:
<tr *ngFor="let bank of bankList; index as i">
<td>{{ bank.name }}</td>
<td>{{ bank.bill }}</td>
<td>{{ bank.card | cardType }}</td>
</tr>
app.module.ts:
import { CardTypePipe } from './card-type.pipe';
#NgModule({
declarations: [
CardTypePipe
],
imports: [..],
providers: [..],
bootstrap: [AppComponent]
})
export class AppModule { }
If you think this pipe is an overkill, you can always use (I personally like my templates to be plain and simple):
<td>{{ bank.card === "D" | "Debit" : "Credit" }}</td>

Exporting Table Contents from Angular to Excel Invokes Error When Attempting To Open Downloaded File

I am making a general application to make a table in Angular and then export that table to excel using the xlsx library and StackBlitz for development. No errors are thrown in the code itself but when I try to open the downloaded file, excel basically tells me that the file extension is invalid.
The error I am encountering
What I've done to debug the issue
Given the nature of the error I've mostly looked for typos in my code such as xslx vs xlsx etc. but to no avail it doesn't seem as if that's the problem. I have tried downloading the table as a CSV which seems to download ok except the table headers are not included (something I am also trying to fix) in the CSV file, but I can then successfully change the file extension from .csv to .xlsx with no problems.
app.component.ts
import { Component, ElementRef, ViewChild } from '#angular/core';
import * as xlsx from 'xlsx';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
#ViewChild('TABLE', { static: false }) TABLE: ElementRef;
title = 'Excel';
constructor() {
console.log('e')
}
xport() {
const workSheet: xlsx.WorkSheet = xlsx.utils.table_to_sheet(this.TABLE.nativeElement)
const workBook: xlsx.WorkBook = xlsx.utils.book_new()
xlsx.utils.book_append_sheet(workBook, workSheet, 'Sheet 1');
xlsx.writeFile(workBook, 'itemSheet.xlsx')
}
header: any = [{
itemNumber: 'Item Number',
itemDescription: 'Item Description',
manufacturer: 'Manufacturer',
manufacturerPartNumber: 'Manufacturer Part Number'
}];
item: any = [{
itemNumber: 1,
itemDescription: 'An Item',
manufacturer: 'Manufacturer A',
manufacturerPartNumber: 123
},{
itemNumber: 2,
itemDescription: 'Another Item',
manufacturer: 'Manufacturer B',
manufacturerPartNumber: 124
},{
itemNumber: 3,
itemDescription: 'Yet Another Item',
manufacturer: 'Manufacturer C',
manufacturerPartNumber: 125
}]
}
app.component.html
<div #TABLE #table>
<div class="row">
<div class="col-sm-12">
<table style="width: 100%" class="table table-bordered">
<thead class = 'thead-dark' *ngFor="let h of header">
<th> {{ h.itemNumber }} </th>
<th> {{ h.manufacturer }} </th>
<th>{{ h.manufacturerPartNumber }}</th>
<th>{{ h.itemDescription }}</th>
</thead>
<tbody>
<tr *ngFor="let i of item">
<td> {{ i.itemNumber }}</td>
<td> {{ i.manufacturer }}</td>
<td> {{ i.manufacturerPartNumber }}</td>
<td> {{ i.itemDescription }}</td>
</tr>
</tbody>
</table>
</div>
<div>
<button (click)="xport()">Export to Excel</button>
</div>
</div>
</div>

Angular: Reset index value in the same template

I have a template of 3 tables having same JSON as parent like this.
<tbody>
<ng-container *ngFor="let row of reportingData.RecommendationData; let i=index">
<tr *ngIf="row.swRecommendations">
<td>{{i+1}}</td>
<td> {{row.swRecommendations.deviceID}}</td>
</tr>
</ng-container>
</tbody>
Another table body
<tbody>
<ng-container *ngFor="let row of reportingData.RecommendationData; let j=index">
<tr *ngIf="row.licenseRecommendations">
<td>{{j+1}}</td>
<td> {{row.licenseRecommendations.deviceID}}</td>
</tr>
</ng-container>
</tbody>
All these tables are in the same template. I'm assigning index values to different variables(i & j) but increment is happening i.e. if first table is having 5 rows, second table is starting with 6 not 1. How to fix this?
I tested you'r code and indexes are starting from 0 .
Please review my code.
Component.html
<h1>First Table</h1>
<table>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
<ng-container>
<tr *ngFor="let a of array;let i = index">
<th>{{i + 1}}</th>
<th>{{a.name}}</th>
</tr>
</ng-container>
</table>
<h1>Second Table</h1>
<table>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
<ng-container>
<tr *ngFor="let a of array;let j = index">
<th>{{j + 1}}</th>
<th>{{a.name}}</th>
</tr>
</ng-container>
</table>
Component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styles: ['./app.component.scss']
})
export class AppComponent {
constructor() {}
public array = [
{ id: 1, name: 'aaa'},
{ id: 2, name: 'bbb'},
{ id: 3, name: 'ccc'},
{ id: 4, name: 'ddd'},
{ id: 5, name: 'eee'},
{ id: 6, name: 'fff'},
]
}
H recently faced the same issue, since we are indexing it and increment it will be like that, the solution of this problem is like this Filter your data in the ts
this.a= this.reportingData.filter(x => x.swRecommendations);
this.b= this.reportingData.filter(x => x.licenseRecommendations);
<tr *ngFor="let row of a"> <tr *ngFor="let row of b">,
and then remove the if condition and iterate the data in HTML like this let row of reportingData , editing needed based on your consition in ts