Display data in HTML table with multi level grouping - html

I have to show some data in the HTML table by grouping using rowspan.
Below is the expected GUI
I have the JSON Data like below. JSON Data here
Angular Code
<table class="table table-fixed" border="1">
<thead>
<tr>
<th>Country</th>
<th>State</th>
<th>City</th>
<th>Street</th>
<th>Male</th>
<th>Female</th>
<th>Others</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let country of Countries">
<tr *ngFor="let item of [].constructor(country.NoOfStreets); let streetIdx = index">
<ng-container *ngFor="let state of country.States; let stateIdx = index">
<td [attr.rowspan]="state.NoOfStreets" style="width: 15%">
{{state.StateName}}
</td>
</ng-container>
<ng-container *ngFor="let state of country.States; let stateIdx = index">
<ng-container *ngFor="let city of state.Cities; let cityIdx = index">
<td [attr.rowspan]="city.NoOfStreets" style="width: 15%">{{city.CityName}}</td>
<ng-container *ngFor="let street of city.Streets; let streetIdx = index">
<td style="width: 15%">{{street.StreetName}}</td>
<td style="width: 15%">{{street.Male}}</td>
<td style="width: 15%">{{street.Female}}</td>
<td style="width: 15%">{{street.Others}}</td>
</ng-container>
</ng-container>
</ng-container>
</tr>
</ng-container>
</tbody>
</table>
I could not able to generate the expected UI. I get the different UI and not getting rendered properly. I tried this one for almost a week and nothing worked out.
The PLUNK version is https://next.plnkr.co/edit/5nYNZ86BiWDke3GE?open=lib%2Fapp.ts&deferRun=1

What you want is to flatten all streats into array, so that you cal loop over it. The flat code will be:
const concat = (x,y) => x.concat(y)
const flatMap = (f,xs) => xs.map(f).reduce(concat, [])
let states = flatMap(c => c.States.map(s => ({Country:c, State: s})), this.Countries);
let cities = flatMap(c => c.State.Cities.map(s => ({Country:c.Country, State:c.State, City: s})), states);
this.streets = flatMap(c => c.City.Streets.map(str => ({Country:c.Country, State:c.State, City: c.City, Street: str})), cities);
And then easily check if each Country, State and City is first in group like:
<tbody>
<tr *ngFor="let str in streets">
<td *ngIf="firstCountryInGroup(str)" [rowspan]="numberOfCountry(str)">
{{str.Country.CountryName}}
</td>
<td *ngIf="firstStateInGroup(str)" [rowspan]="numberOfStatse(str)">
{{str.State.CityName}}
</td>
<td *ngIf="firstCityInGroup(str)" [rowspan]="numberOfCities(str)">
{{str.City.CityName}}
</td>
<td>{{str.Street.Name}}<td>
<td>{{str.Street.Male}}<td>
<td>{{str.Street.Female}}<td>
<td>{{str.StreetOthers}}<td>
</tr>
</tbody>

We need to have separate columns where we run a loop based on child or sibling - you will get the idea from the comments in the code below also
relevant TS:
<table class="table table-fixed" border="1">
<thead>
<tr>
<th>Country</th>
<th>State</th>
<th>City</th>
<th>Street</th>
<th>Male</th>
<th>Female</th>
<th>Others</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let country of Countries; let i = index">
<tr>
<!-- column 1 -->
<td>{{country.CountryName}}</td>
<!-- column 2 -->
<td>
<ng-container *ngFor="let state of country.States">
<tr>
<td> {{state.StateName}} </td>
</tr>
</ng-container>
</td>
<!-- column 3 -->
<td>
<ng-container *ngFor="let state of country.States">
<tr>
<td>
<ng-container *ngFor="let city of state.Cities">
<tr>
<td> {{city.CityName}} </td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
<!-- column 4 -->
<td>
<ng-container *ngFor="let state of country.States">
<tr>
<td>
<ng-container *ngFor="let city of state.Cities">
<tr>
<td>
<ng-container *ngFor="let street of city.Streets">
<tr>
<td>
{{street.StreetName}}
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
<!-- column 5 -->
<td>
<ng-container *ngFor="let state of country.States">
<tr>
<td>
<ng-container *ngFor="let city of state.Cities">
<tr>
<td>
<ng-container *ngFor="let street of city.Streets">
<tr>
<td>
{{street.Male}}
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
<!-- column 6 -->
<td>
<ng-container *ngFor="let state of country.States">
<tr>
<td>
<ng-container *ngFor="let city of state.Cities">
<tr>
<td>
<ng-container *ngFor="let street of city.Streets">
<tr>
<td>
{{street.Female}}
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
<!-- column 7 -->
<td>
<ng-container *ngFor="let state of country.States">
<tr>
<td>
<ng-container *ngFor="let city of state.Cities">
<tr>
<td>
<ng-container *ngFor="let street of city.Streets">
<tr>
<td>
{{street.Others}}
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</td>
</tr>
</ng-container>
</tbody>
</table>
working stackblitz here

With this solution
https://stackblitz.com/edit/angular-gbhcun?file=src/app/app.component.html
Add below style to meet exact output
table, th, td {
border: 1px solid black;
}

Related

how to prevent Angular table header is repeating

<div *ngFor = "let recordData of employees.record">
<div *ngIf = 'recordData.queryName==="style-size-query"'>
<table class ="table">
<thead>
<tr> product </tr>
<tr> size </tr>
<tr> sortOrder </tr>
</thead>
<tbody>
<tr>
<td> {{recordData.data.product}} </td>
<td> {{recordData.data.size}} </td>
<td> {{recordData.data.sortOrder}} </td>
</tr>
</tbody>
</table>
</div>
</div>
// **this is Angular code and this is the output this code is working but table header data is repeating after the each iteration.
I used ngFor for iteration the record array (it is in the first photo) loop. I used the ngIf to select the queryName and there are six record will come (it is in the first photo). I want that six record under a one table
**
Just moved structural directives and used ng-container to use ngIf and ngFor
I don't want to add extra element like div or span to apply ngIf and ngFor condition. that's why I used ng-container.
<div>
<div>
<table class ="table">
<thead>
<tr> product </tr>
<tr> size </tr>
<tr> sortOrder </tr>
</thead>
<tbody>
<ng-container *ngFor = "let recordData of employees.record">
<ng-container *ngIf = 'recordData.queryName==="style-size-query"'>
<tr>
<td> {{recordData.data.product}} </td>
<td> {{recordData.data.size}} </td>
<td> {{recordData.data.sortOrder}} </td>
</tr>
</ng-container>
</ng-container>
</tbody>
</table>
</div>
</div>
Please try like this
<table class ="table">
<thead>
<tr> product </tr>
<tr> size </tr>
<tr> sortOrder </tr>
</thead>
<div *ngIf = 'recordData.queryName==="style-size-query"'>
<tbody>
<tr>
<td> {{recordData.data.product}} </td>
<td> {{recordData.data.size}} </td>
<td> {{recordData.data.sortOrder}} </td>
</tr>
</tbody>
</div>
</table>

How do I hide my table element when the component is empty?

The html is printing all the API data in the table. Products and Components
The table should not print the product when the components are empty. What is the best way to do this?
I'm using angular 8
products = {
"id": 1,
"name": "John",
"components": [
{
"id": 130,
"name": "Price",
}
]
"isSelected": false }
products = {
"id": 2,
"name": "name",
"components": [] }
<div class="card">
<div class="card-body">
<table class="table table-striped table-bordered hover">
<thead>
<tr>
<th width="1%" class="text-center"></th>
<th width="8%" class="text-center">Id</th>
<th width="8%" class="text-center">Name</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let product of products; index as i">
<tr>
<td>
<div>
<button class="btn btn-sm btn-default"></button>
</div>
</td>
<td>{{product.id}}</td>
<td>{{product.name}}</td>
</tr>
<tr *ngFor="let prodComp of product.components">
<td></td>
<td>{{prodComp.id}}</td>
<td>{{prodComp.name}}</td>
</tr>
</ng-container>
</tbody>
</table>
</div>
</div>
You could add another container inside your *ngFor using *ngIf to test for that condition:
<ng-container *ngFor="let product of products; index as i">
<ng-container *ngIf="product.components.length > 0">
<tr>
<td>
<div>
<button class="btn btn-sm btn-default"></button>
</div>
</td>
<td>{{product.id}}</td>
<td>{{product.name}}</td>
</tr>
<tr *ngFor="let prodComp of product.components">
<td></td>
<td>{{prodComp.id}}</td>
<td>{{prodComp.name}}</td>
</tr>
</ng-container>
</ng-container>

Generate colums w/ Angular Material using an *ngFor

I'm using a tutorial that takes data from the component.
Here I have to insert every column by myself, instead I want to generate them like the example on the bottom of the page.
<div>
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Id </th>
<td mat-cell *matCellDef="let user"> {{user.id}} </td>
</ng-container>
<ng-container matColumnDef="firstName">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<td mat-cell *matCellDef="let user"> {{user.firstName}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
Here I wasn't using Material, I generated every column from a string[]:
getColumns(): string[] {
return ['id', 'firstName', 'lastName', 'age'];
}//this is in my service
So if I delete or modify a column in my service, it will be removed in the table.
<table>
<tr>
<th *ngFor="let col of columns" >{{col}}
</th>
</tr>
<tr *ngFor="let user of users">
<td *ngFor="let col of columns">{{user[col]}}</td>
</tr>
</table>
<div>
</div>
How can I apply the same method in the ng-container, th and td's?
You Can Use Like This.
<table class="table table-bordered table-info">
<thead class="bg-primary text-white">
<tr>
<th>Product</th>
<th>Qty</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr *ngFor='let kotitems of cartitems'>
<td>{{kotitems.Product}}</td>
<td>{{kotitems.Qty}}</td>
<td>{{kotitems.Amt}}</td>
</tr>
</tbody>
<tfoot class="bg-primary text-white">
<tr>
<td>Total</td>
<td colspan="2" style="text-align: center;">{{getSum()}}</td>
</tr>
</tfoot>
</table>

Convert table cell to editable input

I want to create a table with 2 columns: Name, Email. Everytime I press the edit button, I want to transform the td into editable inputs. The problem is that if I have more users and I press the edit button, all users will become editable, not just the selected one. How can I solve this problem?
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of usersList">
<td *ngIf="!editUser">{{ user.name }}</td>
<td *ngIf="editUser"><input class=" form-control" size="1" type="text" [(ngModel)]="user.name"></td>
<td *ngIf="!editUser">{{ user.email }}
<td *ngIf="editUser"><input class=" form-control" size="1" type="text" [(ngModel)]="user.email"></td>
<td *ngIf="!editUser">
<a class="action-btn" (click)="onEdit()">
<p class="material-icons pointer">edit</p>
</a>
</td>
</tr>
</tbody>
</table>
editUser: boolean = false
onEdit() {
this.editUser = !this.editUser
}
How the table looks before pressing the red button
How the table looks after pressing the button
Thank you for your time! (this is what I want to achieve
Do you have an id for the user?
Then you could do something like:
<tbody>
<tr *ngFor="let user of usersList">
<td *ngIf="editUserId !== user.id">{{ user.name }}</td>
<td *ngIf="editUserId === user.id"><input [(ngModel)]="user.name"></td>
<td *ngIf="editUserId !== user.id">{{ user.email }}
<td *ngIf="editUserId === user.id"><input [(ngModel)]="user.email"></td>
<td *ngIf="editUser !== user.id">
<a class="action-btn" (click)="onEdit(user.id)">
<p class="material-icons pointer">edit</p>
</a>
</td>
</tr>
</tbody>
and
editUserId: number;
onEdit(userId: number) {
this.editUserId = userId;
}
Try this, it will work for you.
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of usersList; index as i">
<td *ngIf="i!=selectedRowIndex">{{ user.name }}</td>
<td *ngIf="selectedRowIndex == i"><input class=" form-control" size="1"
type="text" [(ngModel)]="user.name"></td>
<td *ngIf="i!=selectedRowIndex">{{ user.email }}
<td *ngIf="selectedRowIndex == i"><input class=" form-control" size="1"
type="text" [(ngModel)]="user.email"></td>
<td>
<a class="action-btn" (click)="onEdit(i)">
<p class="material-icons pointer">edit</p>
</a>
</td>
</tr>
</tbody>
</table>
selectedRowIndex = -1
onEdit(rowIndex) {
this.selectedRowIndex = rowIndex;
}

Display [object Object] in html Angular

I have a problem to display data array in table.
I have component.html
<div class="row">
<table class="bordered table-bordered" [mfData]="hbps| dataFilter : filterQuery" #mf="mfDataTable" [mfRowsOnPage]="rowsOnPage" [(mfSortBy)]="sortBy" [(mfSortOrder)]="sortOrder">
<thead>
<tr>
<th>
<mfDefaultSorter by="serial_number">Serial Number</mfDefaultSorter>
</th>
<th>
<mfDefaultSorter by="sensor_serial">Sensor Serial</mfDefaultSorter>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of mf.data">
<td>{{item.serial_number}}</td>
<ul><li>{{item.sensors[0].sensor_serial}}</li></ul>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<mfBootstrapPaginator [rowsOnPageSet]="[5,10,15]"></mfBootstrapPaginator>
</td>
</tr>
</tfoot> </table> </div>
component.ts
> public hbps: HBPS[];
>
> ngOnInit() {
> this.ws.hbpsGetAll().subscribe(
> hbps=> {
> this.hbps= hbps;
> }
> );
> }
ws send me this data
{"StatusCode":0,"StatusMessage":"OK","StatusDescription": [{"homeboxpackage_id":"1","active":0,"homebox_id":"11","serial_number":"serialn1","user_id":"31",
"sensors":{ "0":{"sensor_serial":"sensorserial1",
"sensors_id":"11E805BA6732C3DB"},
"1":{"sensor_serial":"sensorserial2",
"sensors_id":"11E805BA6F1E6CE9"},
"2":{"sensor_serial":"sensorserial3",
"sensors_id":"11E805BA7716C775"}}}]
And, my object sensors: string[]
Can you help me to display all array sensors in cell of the table.