I'm using Angular 4/5 and I have to create several tables in the Angular based on a string value. Here is the model which I've made to create a table.
class NameValuePair {
Name: string;
Value: string;
}
export class Family {
Properties: Array<NameValuePair>;
PropertyType: string;
}
Given below is the hard-coded values which the table will contain.
export const list1: Family[] =
[
{
Properties: [
{
Name: "A",
Value: "1"
},
{
Name: "B",
Value: "2"
},
{
Name: "C",
Value: "3"
}
],
PropertyType: "Type 1"
},
{
Properties: [
{
Name: "A",
Value: "10"
},
{
Name: "B",
Value: "20"
},
{
Name: "C",
Value: "30"
}
],
PropertyType: "Type 1"
},
{
Properties: [
{
Name: "A",
Value: "100"
},
{
Name: "B",
Value: "200"
},
{
Name: "C",
Value: "300"
}
],
PropertyType: "Type 2"
}
]
Now, the main thing to note here is that the tables will be created based on the PropertyType. As in the above structure, the PropertyType of the first two elements of the array is same i.e. Type 1 so 2 tables will be created. One with the caption/heading: Type 1 and other with the caption: Type 2.
The properties[] array of the second array element will become the second row of the first table. I'm not able to find the logic on how do I create the tables based on this PropertyType string value. However, that's what I wrote in the component.html file but this logic is incorrect.
<div class="container pt-4" *ngFor="let element of list;let i = index">
<ng-container *ngIf="list[i].PropertyType == list[i+1].PropertyType">
<div style="padding-left:250px;font-size: 20px" class="pb-2">{{element.PropertyType}}</div>
<table id="{{element.PropertyType}}" class="table table-striped table-bordered table-responsive pb-3 mx-auto">
<thead style="height:40px">
<tr align="center">
<th *ngFor="let property of element.Properties" style="font-size: 20px">{{property.Name}}</th>
</tr>
</thead>
<ng-container *ngFor="let element1 of list">
<tr align="center" *ngIf="element.PropertyType == element1.PropertyType">
<td *ngFor="let property of element1.Properties; let propertyIndex = index" style="width: 200px">
<ng-container [ngSwitch]="propertyIndex">
<div *ngSwitchDefault style="font-size: 20px">{{property.Value}}</div>
</ng-container>
</td>
</tr>
</ng-container>
</table>
</ng-container>
</div>
list here refers to the list1 const array as mentioned above. Please help.
This is complete logic, I did not add css, since that is not asked. Use this it is working https://stackblitz.com/edit/angular-hd9jey
import {
Component
} from '#angular/core';
import {
list1
} from './list1';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
})
export class AppComponent {
tableArray = [];
constructor() {
let tableObject = {};
list1.forEach(i => {
if (tableObject[i.PropertyType]) {
tableObject[i.PropertyType].push(i);
} else {
tableObject[i.PropertyType] = [i];
}
this.tableArray = Object.entries(tableObject);
});
}
}
<div class="container pt-4" *ngFor="let table of tableArray;index as i">
<div style="padding-left:250px;font-size: 20px" class="pb-2">{{table[0]}}</div>
<table id="{{table[0]}}" class="table table-striped table-bordered table-responsive pb-3 mx-auto">
<thead style="height:40px">
<tr align="center">
<th *ngFor="let property of table[1][0].Properties" style="font-size: 20px">
{{property.Name}}</th>
</tr>
</thead>
<tr *ngFor="let property of table[1];" align="center">
<td *ngFor="let va of property.Properties">
{{va.Value}}
</td>
</tr>
</table>
</div>
You want to manipulate your list1 data into a nicer format first:
e.g.
export interface CustomTable {
header: string,
rows: Properties[]
}
const tables: CustomTable[] [];
list1.forEach(family => {
// Check if this type is already in the table.
const myTable: CustomTable = tables.find(customTable => customTable.header === family.propertyType);
if (myTable) {
// If it is, then add a new row
myTable.rows.push(family.Properties);
} else {
// If not, create a new table
myTable.rows.push({
header: family.propertyType,
rows: [family.Properties]
});
}
});
Then change your html accordingly. You probably want
<ng-container ngFor="let table of tables">
and within that somewhere a:
<tr *ngFor=let row of tables.rows>
Related
I've got a nested array that I would like to display in a table. However, I can't get my nested array to show correctly.
My data set looks like this:
[
{
"dd":"February",
"md":[
{ "dag":"2020-02-01" },
{ "dag":"2020-02-02" },
{ "dag":"2020-02-03" }
]
},
{
"dd":"March",
"md":[
{ "dag":"2020-03-01" },
{ "dag":"2020-03-02" },
{ "dag":"2020-03-03" }
]
}
]
I would like a table which look like this.
| February | March |
| 2020-02-01 | 2020-03-01 |
| 2020-02-02 | 2020-03-02 |
| 2020-02-03 | 2020-03-03 |
I got this working, but it gives me 2 tables instead of one.
<template v-for="(md2, index) in md2s">
<table :key=index >
<thead >
<tr align="center">
<th style="width: 80px">{{md2}}</th>
</tr>
</thead>
<tr v-for="(date, index) in md2.md" :key=index>
<td align="center" >{{date.dag }}</td>
</tr>
</table>
</template>
All help is appreciated.
br. Erik
You could use a different way to create the loop (one table, multiple columns)
In this case, to populate each header with 'dd' and each column with md elements.
var data=[
{
"dd":"February",
"md":[
{
"dag":"2020-02-01"
},
{
"dag":"2020-02-02"
},
{
"dag":"2020-02-03"
}
]
},
{
"dd":"March",
"md":[
{
"dag":"2020-03-01"
},
{
"dag":"2020-03-02"
},
{
"dag":"2020-03-03"
}
]
}
];
new Vue({
el:'#app',
data:{
md2s: data
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.8/vue.js"></script>
<div id=app >
<table >
<thead >
<tr align="center">
<th v-for="(md2, index) in md2s" :key=index style="width: 80px">{{md2.dd}}</th>
</tr>
</thead>
<tbody>
<tr align="center">
<td v-for="(md2, index) in md2s" :key=index style="width: 80px">
<div v-for="(mdcol, col) in md2.md" :key=col>
{{mdcol.dag}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
https://jsfiddle.net/bn5g1v09/1/
What you need is two diferent iterations. One for the header and another for the table body. For the header, all you need is to add the month name on order. The snippet shows with the computed property months how to do it. This completes the header iteration and the first.
The second one is a little more complex. You need to know beforehand how many lines there will be, for that I made a computed property maxLength that searches over each md and gives the greater one. Then for each row iterate over each month and then verify if the month has enough dates with v-if and if it does look up the desired date from the index and the nested data sctructure. That resumes the second iteration.
The below snippet is a working example with a more complex data showing what could happen with different md sizes and automatic month ordering.
var app = new Vue({
el: '#app',
data: () => ({
nested: [
{ "dd": "February",
"md": [{ "dag": "2020-02-01" },{ "dag": "2020-02-02" },{ "dag": "2020-02-03" },{ "dag": "2020-03-04" }]
},
{ "dd": "March",
"md": [{ "dag": "2020-03-01" },{ "dag": "2020-03-02" },{ "dag": "2020-03-03" }]
},
{ "dd": "January",
"md": [{ "dag": "2020-01-01" }]
}
]
}),
computed: {
staticMonths() {
return Array.from(Array(12),(e,i)=>new Date(25e8*++i).toLocaleString('en-US',{month: 'long'}));
},
months() {
return this.nested.map(item => item.dd).sort((a, b) => {
const A = this.staticMonths.indexOf(a);
const B = this.staticMonths.indexOf(b);
return A-B;
});
},
maxLength() {
return this.nested.reduce((accum, curr) => accum > curr.md.length ? accum : curr.md.length, 0);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<thead>
<tr>
<th v-for="(item, index) in months">{{ item }}</th>
</tr>
</thead>
<tbody>
<tr v-for="index in maxLength">
<td v-for="item in months">
<span v-if="nested.find(nest => nest.dd === item).md.length > index-1">
{{nested.find(nest=>nest.dd===item).md[index-1].dag}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
I have a table with multiple columns that I currently filter using a text input (search) field:
HTML (simplified):
<div class="search">
<input type="text" placeholder="Search" data-ng-model="vm.filter_on" />
</div>
<tr data-ng-repeat="product in vm.data | filter: vm.filter_on>
<td>{{product.id}}</td>
<td>{{product.name}}</td>
<td>{{product.brand}}</td>
</tr>
Let's say I have these three products:
{
id: 1,
name: "Waffles",
brand: "Walmart",
},
{
name: "Pizza",
brand: "Walmart",
},
{
name: "Soda",
brand: "Target",
}
If I enter "Walmart" in the search bar, I will see the first two objects. What I want to know is if it's possible to search "Walmart piz" and only be shown the second object--essentially, have the search term try to match across values from multiple columns.
Most of what I've found when looking for a solution has been about trying to set the specific columns a search will consider, but I haven't found anything that solves my exact use case.
I created a workaround using the nifty filter from this question, which solves the issue of searching with multiple fragments rather than full terms: AngularJS filter for multiple strings
But even then, I still need to combine the column data into a single string for the search to work. Is there any way to do this more elegantly in Angular?
You should create custom filter:
angular.module('app', []).controller('ctrl', function($scope) {
var vm = this;
vm.filter_on = "Walmart piz";
vm.data = [
{ id: 1, name: "Waffles", brand: "Walmart" },
{ name: "Pizza", brand: "Walmart" },
{ name: "Soda", brand: "Target" }
]
}).filter('custom', function(){
return function(input, search){
if(!search)
return input;
var items = search.split(' ').filter(x => x).map(x => x.toLowerCase());
return input.filter(x => {
for(var item of items){
var flag = false;
for(var prop in x){
if(prop != '$$hashKey' && (x[prop] + '').toLowerCase().indexOf(item) != -1){
flag = true;
break;
}
}
if(!flag)
return false;
}
return true;
})
}
})
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js">
</script>
<div class="search" ng-app='app' ng-controller='ctrl as vm'>
<input type="text" placeholder="Search" ng-model="vm.filter_on" />
<br>
<br>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>brand</th>
<tr>
</thead>
<tbody>
<tr data-ng-repeat="product in vm.data | custom: vm.filter_on">
<td>{{product.id}}</td>
<td>{{product.name}}</td>
<td>{{product.brand}}</td>
</tr>
</tbody>
</table>
</div>
Does the dataKey property on p-table need to be defined as a column or does it simply need to be a property of the objects in the [value] array? If it needs to be a column, does this column need to be visible?
No, the dataKey does not need to be a column.
The dataKey should be a property of the record, but it doesn't need to be displayed in order to be used by the table.
HTML:
<p-table [columns]="cols" [value]="cars" [(selection)]="selectedCars" dataKey="vin">
<ng-template pTemplate="header" let-columns>
<th *ngFor="let col of columns">
{{col.header}}
</th>
</ng-template>
<ng-template pTemplate="body" let-car>
<tr>
<td>{{car.year}}</td>
</tr>
</ng-template>
</p-table>
Typescript:
export class TableDemo implements OnInit {
cars: Car[];
cols: any[];
constructor() { }
ngOnInit() {
this.cars = [
{ vin: '123ABC', year: 1994 },
{ vin: '234BCD', year: 1978 },
{ vin: '345CDE', year: 2015 },
];
this.cols = [
{ field: 'year', header: 'Year' }
];
}
}
PrimeNG Table Documentation
I am looking a solution for dynamic data structure(inconsistent like different property name and property length) with ng-repeat. sample code are below.
$scope.data = [{
"table":[{
"employee":"name1",
"value1":"10",
"value2":"20"
},
{
"employee":"name2",
"value1":"15",
"value2":"30"
}]
},
{
"table":[{
"company":"name1",
"compnayValue":"12"
},
{
"company":"name2",
"compnayValue":"12"
}]
}]
<ul>
<li ng-repeat="item in data">
<table>
<tr ng-repeat="row in item.table">
<td>{{??}}</td>
<td>{{??}}</td>
</tr>
</table>
</li>
</ul>
You could enumerate all properties and display their values by another ng-repeat on td:
<li ng-repeat="item in data">
<table>
<tr ng-repeat="row in item.table">
<td ng-repeat="(key, value) in row">
{{row[key]}}
</td>
</tr>
</table>
</li>
but that would break the tabular format of data since some rows would have more tds. To prevent that you could first find out the set of all keys on all rows, do a th repeat with those first and then display them on the corresponding td below, e.g.:
<th ng-repeat="propertyName in allPossiblePropertyNames">
{{propertyName}}
</th>
and
<td ng-repeat="propertyName in allPossiblePropertyNames">
{{row[propertyName ]}}
</td>
Use <tbody> to represent an object inside table array and (key,value) syntax mentioned in iterating over object properties section to iterate over it's properties like:
angular.module('test', []).controller('testCtrl', function($scope) {
$scope.data = [{
"table": [{
"employee": "name1",
"value1": "10",
"value2": "20"
}, {
"employee": "name2",
"value1": "15",
"value2": "30"
}]
}, {
"table": [{
"company": "name1",
"compnayValue": "12"
}, {
"company": "name2",
"compnayValue": "12"
}]
}]
});
ul {
padding: 0;
}
ul li {
list-style-type: none;
margin-bottom: 10px;
}
table {
width: 100%;
table-layout: fixed;
background: #ebebeb;
}
tbody:nth-child(odd) tr {
color: #fff;
background: dodgerblue;
}
tbody:nth-child(even) tr {
color: #fff;
background: hotpink;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="testCtrl">
<ul>
<li ng-repeat="item in data">
<table>
<tbody ng-repeat="row in item.table">
<tr ng-repeat="(key, value) in row">
<td>
{{key}}
</td>
<td>
{{value}}
</td>
</tr>
</tbody>
</table>
</li>
</ul>
</div>
Check this plunker, you can define template depends on your data :
https://plnkr.co/edit/fVGhKZy5gnBzuPwspy5s?p=preview
Use angular filter :
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.data = [{
"table":[{
"employee":"name1",
"value1":"10",
"value2":"20"
},
{
"employee":"name2",
"value1":"15",
"value2":"30"
}]
},
{
"table":[{
"company":"name1",
"compnayValue":"12"
},
{
"company":"name2",
"compnayValue":"12"
}]
}]
})
.filter('isCompnay', function() {
return function(input) {
console.log(input.employee === undefined)
return input.company ? input : undefined;
};
})
.filter('isEmployee', function() {
return function(input) {
console.log(input.employee === undefined)
return input.employee ? input : undefined;
};
});
I currently have a nested ngRepeat, where the inner loop iterates over a collection of items from its parent. An excerpt:
<div ng-repeat="person in persons">
(Irrelevant code here.)
<table>
<tr>
<th ng-click="setItemOrder('name')">Item name</th>
<th ng-click="setItemOrder('number')">Item number</th>
</tr>
<tr ng-repeat="item in person.items | orderBy:itemOrder">
<td>{{item.name}}
<td>{{item.number}}
</tr>
</table>
</div>
By clicking the table headers, I set the itemOrder-property in my controller to the name of the property I want orderBy to use:
$scope.setItemOrder = function(order){
$scope.itemOrder = order;
}
This all works fine, except that if I click the headers in one person-div, the item-tables in all person-divs get sorted on that property.
Is there a way to make ngRepeat only apply orderBy to entries that match a certain criteria - for instance a certain index? Or should I use a different approach?
Try setting the property to respective person instance as follows:
angular.module('test', []).controller('testController', function($scope) {
$scope.persons = [{
items: [{
name: 'test',
number: 2
}, {
name: 'test1',
number: 1
}]
}, {
items: [{
name: 'test3',
number: 5
}, {
name: 'test4',
number: 4
}]
}];
$scope.setItemOrder = function(person, order) {
person.itemOrder = order;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form ng-app="test" ng-controller="testController">
<div ng-repeat="person in persons">
<table>
<tr>
<th ng-click="setItemOrder(person,'name')">Item name</th>
<th ng-click="setItemOrder(person,'number')">Item number</th>
</tr>
<tr ng-repeat="item in person.items | orderBy:person.itemOrder">
<td>{{item.name}}
<td>{{item.number}}
</tr>
</table>
</div>
You could add a ordering variable for each person and extend setItemOrder with the person object. Then you can call:
setItemOrder(person, 'name');
and then use it in the ngRepeat:
orderBy:person.itemOrder
angular.module('test', []).controller('testController', function($scope) {
$scope.ordersort=true;
$scope.orderfield='number';
$scope.persons = {
"items": [{
"name": 'test',
"number": 2
}, {
"name": 'test1',
"number": 1
}],
"item1": [{
"name": 'test3',
"number": 5
}, {
"name": 'test4',
"number": 4
}]
};
$scope.setItemOrder = function(person, order) {
$scope.orderfield=order;
person.itemOrder = order;
$scope.ordersort= !$scope.ordersort;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form ng-app="test" ng-controller="testController">
<div ng-repeat="person in persons">
<table>
<tr>
<th ng-click="setItemOrder(person,'name')">Item name</th>
<th ng-click="setItemOrder(person,'number')">Item number</th>
</tr>
<tr ng-repeat="item in person | orderBy:orderfield:ordersort">
<td>{{item.name}}
<td>{{item.number}}
</tr>
</table>
</div>
I have modified your example. In this example table sorting is working perfectly. But It is not sorted the particular table when I click on that table header. Anyway to sort columns by specific table?
angular.module('test', []).controller('testController', function($scope) {
$scope.persons = [{
items: [{
name: 'test',
number: 2
}, {
name: 'test1',
number: 1
}]
}, {
items: [{
name: 'test3',
number: 5
}, {
name: 'test4',
number: 4
}]
}];
$scope.setItemOrder = function(person, order) {
person.itemOrder = order;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form ng-app="test" ng-controller="testController">
<div ng-repeat="person in persons">
<table>
<tr>
<th ng-click="setItemOrder(person,'name')">Item name</th>
<th ng-click="setItemOrder(person,'number')">Item number</th>
</tr>
<tr ng-repeat="item in person.items | orderBy:person.itemOrder">
<td>{{item.name}}
<td>{{item.number}}
</tr>
</table>
</div>