Display multi arrays with rowspan in angular - html

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"

Related

How to combine two arrays into one array of objects?

Can I use an ngFor instead of repeating <table> two times?
NB: I thought to combine all the items into objects as items of a single array of mapping(each object contains a variable, label and value) but it does not work for me)
....
this.maxValueTable.push(selectedData.res.maxValue);
this.minValueTable.push(selectedData.res.minValue);
...
<div style="display: flex;">
<table style="width:100%;">
<thead>
<tr>
<th>Max</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let maxValue of maxValueTable">
<td> {{ maxValue | numberFormatter: (getUnit() | async)}}</td>
</tr>
</tbody>
</table>
<table style="width:100%;">
<thead>
<tr>
<th>Min</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let maxValue of minValueTable">
<td> {{ MinValue| numberFormatter: (getUnit() | async)}}</td>
</tr>
</tbody>
</table>
</div>
Another way:
<!--create an array directly in the .html: you can also use
a variable in your .ts-->
<table *ngFor="let table of [{head:'Max',data:maxValueTable},
{head:'Max',data:maxValueTable}]
style="width:100%;">
<thead>
<tr>
<!--use table.head-->
<th>{{table.head}}</th>
</tr>
</thead>
<tbody>
<!--see how iterate over table.data-->
<tr *ngFor="let maxValue of table.data">
<td> {{ maxValue | numberFormatter: (getUnit() | async)}}</td>
</tr>
</tbody>
</table>
If your arrays has the same length and only want a table, iterate over one array ans use the index to get the value of the another one
<table style="width:100%;">
<thead>
<tr>
<th>Max</th>
<th>Min</th>
</tr>
</thead>
<tbody>
<!--see the "let i=index"-->
<tr *ngFor="let maxValue of maxValueTable;let i=index">
<td> {{ maxValue | numberFormatter: (getUnit() | async)}}</td>
<!--use the "index" "i" to get the element of the another array-->
<td>
{{ minValueTable[i] | numberFormatter: (getUnit() | async)}}
</td>
</tr>
</tbody>
</table>
in this case you can also use map to create a new Array
minMax=this.minValueTable.map((x,index)=>({
min:x,
max:this.maxValueTable[index]
}))
And use {{value.min}} and {{value.max}}
You can create a function that will combine min and max values into an array like that:
mergeIntoArray(maxValue: Array<number>, minValue: Array<number>): IMergedObj[] {
let mergedArray = [];
maxValue.forEach((value, index) => {
let tempObj = {};
tempObj['maxValue'] = value;
tempObj['minValue'] = minValue[index];
mergedArray.push(tempObj);
});
return mergedArray;
}
and call this function like that:
let minAndMax = [];
this.minAndMax = this.mergeIntoArray(this.maxValueTable, this.minValueTable);
after that, use this variable (minAndMax) in your HTML template. This way you do not need to use ngFor twice.
<table style="width:100%;">
<thead>
<tr>
<th>Max</th>
<th>Min</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of minAndMax">
<td>{{ item.maxValue }}</td>
<td>{{ item.minValue }}</td>
</tr>
</tbody>
</table>
Here is the stackblitz link created for you. Hope that might help you.
You can use *ngTemplateOutlet for this case:
<div style="display: flex;">
<ng-container *ngTemplateOutlet="tableTemplate; context: {$implicit: maxValueTable, header: 'Max'}"></ng-container>
<ng-container *ngTemplateOutlet="tableTemplate; context: {$implicit: minValueTable, header: 'Min'}"></ng-container>
</div>
<ng-template #tableTemplate let-values, let-header="header">
<table style="width:100%;">
<thead>
<tr>
<th>{{ header }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let value of values">
<td> {{ value | numberFormatter: (getUnit() | async) }}</td>
</tr>
</tbody>
</table>
</ng-template>
And you will get the same table rendered twice - for Max and Min values.
As you can see, the arguments are passed as a second argument inside ngTemplateOutlet:
context: {$implicit: valuesArgument, header: headerArgument}
Later you could use this template to create infinite amount of tables:
<div *ngFor="let table of tables">
<ng-container *ngTemplateOutlet="tableTemplate; context: {$implicit: table.values, header: table.header}"></ng-container>
</div>
Assuming your tables property will look like
export class C {
tables: Table[] = [{header: 'Min', values: [1, 2, 3]}, {header: 'Max', values: [4, 5, 6]}, {header: 'Other', values: [0, -1, -2]}]
}

Is there a way I can incorporate mailto links with ngFor?

I create an Object and use ngFor to loop through it and create a table. I would like to somehow incorporate mailto links if possible.
I have this finalResults object that looks something like this:
[
{key: 'example#example.com', value: 'message'}
]
Where message is something like
"Please contact the Login Team for help."
I would like to make Login Team a mailto link.
I have already tried making the message like this
Please contact the Login Team for help.
But on the webpage it just displayed that exactly. There was no link.
I use ngFor to loop through my object and make a table.
< <tr>
<th *ngFor="let col of displayedColumns">
{{ col }}
</th>
</tr>
<tr *ngFor="let item of finalResults">
<td>{{ item.key }}</td>
<td>{{ item.value }}</td>
</tr>
<tr></tr>
</table>
Is there some way to do this?
If you want to keep your text as html text then consider using innerHTML binding:
ts
finalResults = [
{
key: 'example#example.com',
value: 'Please contact the Login Team for help.'
}
];
html
<td [innerHTML]="item.value"></td>
Ng-run Example
In component declare
email: string = 'mailto:misesasd#gmail.com'
in template:
<a [href]="email">misesasd#gmail.com</a>
array:
<tr *ngFor="let item of finalResults">
<td><a [href]="item.key">{{item.key}}</a></td>
<td>{{ item.value }}</td>
</tr>
or:
<tr *ngFor="let item of finalResults">
<td>{{item.key}}</td>
<td>{{ item.value }}</td>
</tr>
<tr *ngFor="let item of finalResults">
<td>{{ item.value }}</td>
</tr>

ngStyle table based on arrays value

I want to obtain a table like this:
I have 2 objects
users = [
{ name: 'john', age: '22' },
{ name: 'mike', age: '20' },
{ name: 'dan', age: '12' },
{ name: 'anne', age: '16' },
{ name: 'jenny', age: '42' },
]
names = [
{ name: 'john', color: 'black' },
{ name: 'mike', color: 'black' },
{ name: 'dan', color: 'red' },
{ name: 'anne', color: 'red' },
{ name: 'jenny', color: 'red' },
]
If a name from names is in users, I want it's color to be black inside table, if not, I want it to be red.
This is my html:
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<ng-container *ngFor="let x of names">
<ng-container *ngIf="x.name == user.name">
<td [ngStyle]="{ 'color': names.color }">{{ user.name }}</td>
<td [ngStyle]="{ 'color': names.color }">{{ user.age }}</td>
</ng-container>
</ng-container>
</tr>
</tbody>
</table>
But it's not working properly. You can see a working snippet here
How can I achieve what I want? Thank you for your time!
You can either create a method that retrieves the color based on name (that you put in your component)
getColor(name) {
return this.names.find(e => e.name === name).color;
}
and call with
<td [style.color]="getColor(user.name)">{{ user.name }}</td>
<td [style.color]="getColor(user.name)">{{ user.age }}</td>
You don't need your double loop when using it like that, but it still needs to do a lookup loop for every iteration.
Better is to combine the two arrays before and use that
combined = [];
constructor() {
this.combined = this.users.map(e => Object.assign(e, this.names.find(x => x.name === e.name)))
}
usage
<tr *ngFor="let user of combined">
<td [style.color]="user.color">{{ user.name }}</td>
<td [style.color]="user.color">{{ user.age }}</td>
</tr>
You have a typo when referencing color. color is a property of the object, not the array you are looping through:
<td [ngStyle]="{ 'color': names.color }">{{ user.name }}</td>
<td [ngStyle]="{ 'color': names.color }">{{ user.age }}</td>
Should be
<td [ngStyle]="{ 'color': x.color }">{{ user.name }}</td>
<td [ngStyle]="{ 'color': x.color }">{{ user.age }}</td>

Dynamic rowspan in angular

Number of persons and their mobiles numbers are dynamic. I need to show this in table.
Data can contain any number of pname and mobile numbers.
dataList = [
{
pname: "abc",
numbers: [{mobile1: 123, mobile2: 234}]
},
{
pname: "mno",
numbers: [{mobile1: 125, mobile2: 237}]
}
]
Template
<tr *ngFor="let data of dataList">
<td [attr.rowspan]="data.numbers.lenght">data.pname</td>
<td>data.numbers</td> // Here how do I show all mobile numbers of the person.
</tr>
Expected output is of below code.
table, th, td {
border: 1px solid black;
}
<table>
<tr>
<th>Pname</th>
<th>Numbers</th>
</tr>
<tr>
<td rowspan="2">Abc</td>
<td>123</td>
</tr>
<tr>
<td>234</td>
</tr>
<tr>
<td rowspan="2">Mno</td>
<td>125</td>
</tr>
<tr>
<td>237</td>
</tr>
</table>
Problem I am facing is I need to add tr after each run of *ngFor and the number of tr will depend on length of numbers array to make row span.
You can do it like shown below
<table>
<ng-container *ngFor="let data of dataList">
<tr>
<td [attr.rowspan]="data.numbers.length">{{data.pname}}</td>
<td>{{data.numbers[0]}}</td>
</tr>
<ng-container *ngFor="let number of data.numbers; let i= index;">
<tr *ngIf="i!=0">
<td>{{number}}</td>
</tr>
</ng-container>
</ng-container>
</table>
But datalist must be of the following format i.e. numbers must be an array
dataList = [
{
pname: "abc",
numbers: [123, 234]
},
{
pname: "mno",
numbers: [125, 237]
}
]
Simple.
You don't need index and nested ng-container.
<table>
<ng-container *ngFor="let data of dataList">
<tr>
<td [attr.rowspan]="data.numbers.length + 1">{{data.pname}}</td>
</tr>
<tr *ngFor="let number of data.numbers;">
<td>{{number}}</td>
</tr>
</ng-container>
</table>
Working example.
https://stackblitz.com/edit/angular-yrose8?file=src%2Fapp%2Fapp.component.html
it is simple to use two *ngFor and avoid repeat Code by data.numbers[0].
<table>
<ng-container *ngFor="let data of dataList">
<ng-container *ngFor="let number of data.numbers; let i= index;">
<tr>
<td *ngIf="i==0" [attr.rowspan]="data.numbers.length">{{data.pname}}</td>
<td>{{number}}</td>
</tr>
</ng-container>
</ng-container>
</table>

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