ng-repeat on table column - html

So I have a List that I am returning from rest service. Now, I want to display this object in column format and now in row one. So it would be something like this:
firstName: Bob Alice
LastName: Doe Joe
EmailId: bob#xyz.com alice#abc.com
ContactNo: 123123 12444
So how can I use ng-repeat over here:
<tr>
<th>firstName:</th>
<td>('Name should be displayed here')</td>
</tr>

You can use ng-repeat on the td element.
<tr>
<th>firstName:</th>
<td ng-repeat="person in people">{{person.firstName}}</td>
</tr>
<tr>
<td ng-repeat="person in people">{{person.lastName}}</td>
</tr>

If you declare the keys of the objects you're iterating through, you can nest your ng-repeats so that you don't need to declare each row over and over. Do the following and nest them instead:
In the Angular App:
/*for the data*/
var personKeys = ['firstName', 'LastName', 'EmailID', 'ContactNo'];
var people = [
{
firstName: 'Bob',
LastName: 'Doe',
EmailID: 'bob#xyz.com',
ContactNo: 123123
},
/* etc */
];
/*don't forget to reference this in your controller with something like: */
this.pKeys = personKeys;
this.people = people;
In your HTML:
<table ng-controller='MyController as personList'>
<tr ng-repeat='key in personList.pKeys'>
<th>{{key}}</th>
<td ng-repeat='person in personList.people'>
{{person[key]}}
</td>
</tr>
</table>
This still has the problem of iterating over each person |attributes| times, instead of just once. I'm sure there is a clever way to only iterate once, memoize, and draw up the table without having to go through your entire data set many times, so that the runtime doesn't grow so quickly as you expand your data.

Related

How to iterate over values of Map values

I would like to fill of this cells, this is initiall view of my app:
-> during app works for each rider rider's score will be fulfilled in such a way:
I have a .ts file where I put values of rider score (it's a map -> key is heat number, value - score of particular rider) (if it will be both string or number depends on further configuration)
export const guests = {
1: new Racer(9, 'Name_1', new Map([['1', 1], ['4', 2], ['7', 4]])),
2: new Racer(10, 'Name_2', new Map([['1', 3], ['3', 4]])),
3: new Racer(11, 'Name_3', new Map([['2', 3], ['6', 2]])),
4: new Racer(12, 'Name_4', new Map([['6', 3], ['10', 3], ['13', 2]]))
};
I would like to get all values of map values in html, however I'm not able to iterate over values of this map.
Further info: There should be all the time displayed all cells (it is designed that there should be exactly the same at the beginning
We don't know how entries for each racer there will be (probably for each rider this will be different)
<table>
<tbody>
<tr *ngFor="let racer of team; let index = index">
<td>{{racer.number}}</td>
<td>{{racer.name}}</td>
<td *ngIf="racer.places">{{racer.places.values().next().value}}</td>
<td *ngIf="racer.places">{{racer.places.values().next().value}}</td>
<td *ngIf="racer.places">{racer.places.values().next().value}}</td>
<td *ngIf="racer.places">{racer.places.values().next().value}}</td>
<td *ngIf="racer.places">{racer.places.values().next().value}}</td>
</tr>
</tbody>
</table>
expected output
instead of only first entry there should be all values from Map
working repo:
https://stackblitz.com/edit/angular-iterate-over-map-values
The way you are initializing the map doesn't create a map after all. Make the following change to guests:
// now it actually contains key-value pairs
const guests = {
1: new Racer(9, "Name_1", { "1": 1, "4": 2, "7": 4 }),
2: new Racer(10, "Name_2", { "1": 3, "3": 4 }),
3: new Racer(11, "Name_3", { "2": 3, "6": 2 }),
4: new Racer(12, "Name_4", { "6": 3, "10": 3, "13": 2 }),
};
now create a new property in the team object called remaining, this holds an array which contains the extra number of columns that are needed for each row. It is dynamically created for each racer.
team = Object.values(guests).map(racer => {
return {
...racer,
remaining: new Array(5 - Object.values(racer.places).length)
}
});
and on the template, iterate over this newly created array for the remaining <td> that should be empty:
<table>
<tbody>
<tr *ngFor="let racer of team">
<td>{{racer.number}}</td>
<td>{{racer.name}}</td>
<td *ngFor="let heat of racer.places | keyvalue">
{{ heat.value }}
</td>
<td *ngFor="let rem of racer.remaining" style="width: 10px;">
</td>
</tr>
</tbody>
</table>
This is some awful data structure to work with.
Simply loop through the range that you need and check for each cell if the data exists with .has() method. Show the data in a ternary expression with .get() method if it exists, or null if it's missing.
Here is the HTML code that should work with your data structure:
<table>
<thead>
<tr>
<td>No.</td>
<td>Racer</td>
<td *ngFor="let p of [1,2,3,4,5]">{{p}}</td>
<td>Sum</td>
<td *ngFor="let p of [6,7]">{{p}}</td>
<td>Sum</td>
</tr>
</thead>
<tbody>
<tr *ngFor="let racer of team; let index = index">
<td>{{racer.number}}</td>
<td>{{racer.name}}</td>
<td *ngFor="let p of [1,2,3,4,5]">{{racer.places.has(""+p) ? racer.places.get(""+p) : null}}</td>
<td>TODO: Sum</td>
<td *ngFor="let p of [6,7]">{{racer.places.has(""+p) ? racer.places.get(""+p) : null}}</td>
<td>TODO: Sum</td>
</tr>
</tbody>
</table>
Unless you are planning to expand the Racer class, I suggest you switch it to an interface and simply use JSON data structure with Objects instead of a Map

How to display the value returned by avg function (mysql) in angular

After executing a query, I received the following data which is in Json format which is in myDocData:
data: [
RowDataPacket {
updatedAt: 2020-01-03T18:30:00.000Z,
email: 'charles#hotmail.com',
name: 'charles',
money_spent: 1,
'avg(e.money_spent)': 1
},
RowDataPacket {
updatedAt: 2020-01-11T18:30:00.000Z,
email: 'ank#gmail.com',
name: 'ank',
money_spent: 10,
'avg(e.money_spent)': 6
}
]
angular code:
<table class="content-table" >
<thead>
<tr>
<th>EMAIL</th>
<th>NAME</th>
<th>money spent</th>
<th>UPDATED</th>
<th>AVERAGE</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of myDocData">
<td>{{item.email}}</td>
<td>{{item.name}}</td>
<td>{{item.money_spent}}</td>
<td>{{item.updatedAt}}</td>
<td>{{item.avg(e.money_spent)}}</td>
</tr>
</tbody>
</table>
Issue is that I am unable to display the value given by avg function
(other attributes are displayed correctly).
Change the line:
<td>{{item.avg(e.money_spent)}}</td>
to:
<td>{{item['avg(e.money_spent)']}}</td>
In JS, you can use square bracket notation to get properties of objects.
If you have the control over the back-end code then you can change the query which is returning "avg(e.money_spent)" to "avg(e.money_spent) as avg_money_spent" in this way will not be facing the issue and you can access the value using
<td>{{item.avg_money_spent}}</td>
Or you could also use the previous answer by #Viqas

Bind first value and remove all other repeating value from a ng-repeat

I have a scenario to bind a html table using angular js. In my table i need to show an a tag based on another column value(Payment Status). If its fully paid no need to show the a tag, else need to show it for very next element. I am a new one in angular.
<table>
<thead>
<tr>
<th>Month</th>
<th>Installement</th>
<th>PaymentAmount</th>
<th>PaymentDate</th>
<th>Payment Status</th>
<th>Pay</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="row in rowCollection|orderBy:type:reverse|filter:searchKeyword|itemsPerPage:maxsize">
<td>{{row.Month}}</td>
<td>{{row.MonthlyInstallement}}</td>
<td>{{row.PaymentAmount}}</td>
<td>{{row.PaymentDate}}</td>
<td>{{row.PaymentStatus}}</td>
<td ng-if="row.PaymentStatus == 'UNPAID'">
Pay Online
</td>
<td ng-if="row.PaymentStatus == 'FULLY_PAID'">
</td>
</tr>
</tbody>
</table>
function bindFinanceDetails() {
var finUrl = baseurl + 'api/FinancialStatement/GetCarFinanceInfo';
var req = {
method: 'post',
data: {
LoginID: LoginID,
ContractNumber: 11170200669,
CustomerId: 2355898046
},
url: finUrl,
headers: {
RequestedPlatform: "Web",
RequestedLanguage: cookiePreferredLanguage,
Logintoken: $cookieStore.get('LoginToken'),
LoginId: LoginID
}
};
$http(req).then(function(response) {
var getData = response.data.FinanceList;
$scope.rowCollection = getData;
}, function(error) {
toastr.error($filter('translate')('Error Occured'));
});
}
A quite hacky solution will be something like the following (just showing you the needed change in the unpaid td element):
<td ng-if="row.PaymentStatus === 'UNPAID'" ng-show="$index === data.payOnlineIndex"
ng-init="data.payOnlineIndex = (!data.payOnlineIndex || (data.payOnlineIndex > $index)) ? $index : data.payOnlineIndex">
Pay Online
</td>
This way ng-init will run for all unpaid elements, setting the smallest index to the payOnlineIndex variable. ng-show will make sure to only show that one element that has the smallest index.
I encapsulate payOnlineIndex with a data object to keep a stable reference to it. This also requires the following addition to the controller code:
$scope.data = { payOnlineIndex: null };
See a working jsFiddle example here: https://jsfiddle.net/t3vktv0r/
Another option is running your filter and orderBy in the controller, searching for the first occurrence of an "unpaid" row, and marking that element for the "pay online" feature with some flag you can test with ng-if in your view.

Vue v-bind - access array object from another array loop

I'm working on a project where I created a table component which is used on multiple pages with different configuration. Every table has it's configuration in a separate file where I store keys, titles and size classes for each column.
Data for each table body come from REST calls and they are loaded dynamically, paginated and then displayed.
<template slot="thead">
<tr>
<th v-for="item in headers" :key="item.id" :class="item.classes">{{item.title}}</th>
</tr>
</template>
<template slot="tbody">
<tr v-for="skill in paginatedSkills"
:key="skill.id"
v-on:click="selectRow(skill)"
v-bind:class="{selectedRow: selectedSkill === skill}"
>
<td class="cell-l">{{skill.name}}</td>
<td class="cell-m">{{skill.owner}}</td>
<td class="cell-s">{{skill.complexity}}</td>
<td class="cell-full">{{skill.description}}</td>
</tr>
</template>
What I want to do is to avoid writing size class for every single cell in the tbody loop. I was hoping to get index of looped object and use it to retrieve the class from config object which is used to populate cells in thead.
<tr v-for="(skill, index) in paginatedSkills" ...>
<td class="{headers[index].classes}">{{skill.name}}</td>
Using index on headers will return the correct item but as a string so obviously classes are not accessible. Any idea how to tweak it?
This options are no go, failing on compile
<td :class="{JSON.parse(headers[index]).classes}">{{skill.name}}</td>
<td :class="{JSON.parse(headers)[index].classes}">{{skill.name}}</td>
<td :class="{{JSON.parse(headers[index]).classes}}">{{skill.name}}</td>
To set class from a variable/property you have two options:
<td v-bind:class="headers[index].classes">{{skill.name}}</td>
<td :class="headers[index].classes">{{skill.name}}</td>
No need for curly braces here since v-bind already expects JS expression.
Update:
What you can also do, is to associate keys of skill object (name, owner, complexity, description) with their header, so each item of headers array will also have for example key property used to access value from skill object:
headers: [
{ id: 1, classes: 'cell-l', title: 'title', key: 'name' },
{ id: 2, classes: 'cell-s', title: 'title', key: 'owner' },
...
]
Thus, your code can be simplified the following way:
<tr v-for="skill in paginatedSkills" ...>
<td v-for="header in headers" v-bind:class="header.classes">{{skill[header.key]}}</td>
</tr>

Angular 2 DataBinding displaying a list in same <td></td>

I am new to Angular2 so any help is much appreciated.
I am getting data in the form of a list<user>. Each user has a list of roles. So in typescript I created this user type:
export class User {
id: string;
firstName: string;
middleName: string;
lastName: string;
email: string;
roles: Role[];
}
I am parsing the data in above type form. 'users' is an array users: Array<User>; in that component.
Displaying the data I want to list out Role[] into comma separated values in like <td>admin, customer, writer</td>,
but what I am getting instead is:
<td>admin</td>
<td>customer</td>
<td>writer</td>
Here is the code I am trying:
<tr *ngFor="let user of users">
<td>{{user.id}}</td>
<td>{{user.firstName +' '+ user.middleName+' '+user.lastName}}</td>
<td>{{user.email}}</td>
<td *ngFor="let role of Role">{{role.name}}</td>
<td><button type="button" class="btn btn-primary " (click)="roleOnClick(user)"></button></td>
</tr>
How can I achieve something like <td> admin, customer, writer</td>?
<td *ngFor="let role of roles">{{role.name}}</td>
That line above says to repeat the <td> tag for each element of the Role array, which is why you're getting the behaviour that you're seeing.
If you just want to get a comma separated list of roles, you may just want to use a simple function call on the role array, for eg:
<td> {{ roles.join(', ') }} </td>
join is a function on the standard JavaScript Array class that allows you to combine the elements of the array simply. The parameter to the function is the text you want to place between each member of the array.