How to create matrix table using json array in Angular - javascript

I need to create one matrix table of each combination by comparing the two array object using Angular. I am explaining my code below.
ColumnNames= ["Colour", "Size"];
configArr={
"Colour":["Black", "Red", "Blue","Grey"],
"size": ["XS","S","XL","XXL","M"]
}
Here I have two array. My first array ColumnNames values will be the table column header and accordingly I need to create the matrix like below.
Expected output::
Colour Size
Black XS
Black S
Black XL
Black XXL
Black M
Red XS
Red S
Red XL
Red XXL
Red M
.................
................
................
Like the above format I need to display the data into angular table. I need only create the matrix dynamically.

A generic solution that works with any configArr, as long as all keys in that object are string[]. Inspired by this.
app.component.ts
import { Component, OnInit } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
name = "Angular";
// Comment in/out properties here to see how the table changes
configArr = {
Colour: ["Black", "Red", "Blue", "Grey"],
Size: ["XS", "S", "XL", "XXL", "M"],
//Price: ["$5", "$10", "$15"],
//Brand: ["B1", "B2", "B3"]
};
// This will be an [] of objects with the same properties as configArr
matrix;
allPossibleCases(arr) {
// Taken almost exactly from the linked code review stack exchange link
}
ngOnInit() {
const configKeys = Object.keys(this.configArr);
const arrOfarrs = configKeys.map(k => this.configArr[k]);
const result = this.allPossibleCases(arrOfarrs);
this.matrix = result.map(r => {
const props = r.split(' ');
let obj = {};
for(let i=0;i<props.length;i++) {
obj[configKeys[i]] = props[i]
}
return obj;
});
}
}
app.component.html
<table>
<thead>
<tr>
<th *ngFor="let kv of configArr | keyvalue">{{ kv.key }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let elem of matrix">
<td *ngFor="let kv of elem | keyvalue">{{ kv.value }}</td>
</tr>
</tbody>
</table>
Stackblitz.
Also, see this for the algorithm behind it.

Try like this:
<table>
<tr>
<th *ngFor="let heading of ColumnNames"> {{heading}} </th>
</tr>
<ng-container *ngFor="let color of configArr.Colour">
<tr *ngFor="let size of configArr.size">
<td>{{color}}</td>
<td>{{size}}</td>
</tr>
</ng-container>
</table>
Working Demo

configArr.Colour.forEach((color) => {
configArr.size.forEach((size) =>{
console.log(color ," ", size); // Change this according to your table
})
})

Related

Show template in loop when value matches template property

I am trying to make a table component, which I have completed, I want to add a feature to the component where I can customize individual cells in the component. I am not exactly sure how to do this.
So I have seen this implemented like the following:
model is the data related to the table (headers, rows, pagination, etc.)
matches is the column name to match (in the headers id what is match, in rows it is the property key).
let-content is the data associated with the cell for that row.
<ui-table [model]="tableModel">
<ng-template matches="columnA" let-content="content">
{{content | commaSeparate}}
</ng-template>
</ui-table>
public tableModel = {
headers: [
{ title: 'Column A', id: 'columnA' },
{ title: 'Column B', id: 'columnB' }
],
rows: [
{ columnA: ['A', 'B'], columnB: 'Column B' },
{ columnA: ['C', 'D'], columnB: 'Column B2' }
]
}
In my ui-table component I have the following table body:
<tbody #tBody class="uitk-c-table__body">
<tr *ngFor="let row of rowItems" class="uitk-c-table__row">
<!-- If "matches === model.headers[x].id" show the custom cell -->
<td *ngFor="let cell of model.headers; index as c">{{row[model.headers[c].id] || ''}}</td>
</tr>
</tbody>
What I am not sure of, is how do I show the custom cell ng-template if matches === model.headers[x].id?
Inside your ui-table component, let's define a input property:
#Input() passedTemplate: TemplateRef<any>;
#Input() displayContent: any // to display variable inside ng-template
And define a input property in order to pass your ng-template to
Like this:
<ui-table [model]="tableModel" [passedTemplate]='template' [displayContent]='content'>
</ui-table>
<ng-template #template matches="columnA" let-content="content">
{{content | commaSeparate}}
</ng-template>
Then try to use ng-container in your ui-table html file:
<ng-container *ngIf="matches === model.headers[x].id"
[ngTemplateOutlet]="passedTemplate"
[ngTemplateOutletContext]="{content: displayContent}">
</ng-container>
Hope this help...
I was able to figure it out. First I create a directive which contains a match property which is the column I want to match on, and a tableCell property which is the template within the directive.
#Directive({ selector: 'ui-table-cell' })
export class TableCellDirective {
#Input() public match: string = '';
#ContentChild('tableCell', { static: false }) public tableCell!: any;
}
Next when the table loads I load all of the templates into an object where the key is the cell id and the value is the template.
export class TableComponent implements AfterContentInit {
#ContentChildren(TableCellDirective)
public cellDirectives!: QueryList<TableCellDirective>;
public columnMeta: { [key: string]: object; } = {};
public ngAfterContentInit() {
this.cellDirectives.toArray().forEach(colData => this.assignColumnMetaInfo(colData));
}
private assignColumnMetaInfo(colData: TableCellDirective) {
const columnMetaInfo: { [key: string]: object; } = {};
columnMetaInfo['tableCell'] = colData.tableCell;
this.columnMeta[colData.match] = columnMetaInfo;
}
}
In the HTML, I then check if the current cell has a template saved if so, I display the template. If not I display the original data.
<tr *ngFor="let row of rowItems" class="uitk-c-table__row">
<td *ngFor="let cell of model.headers">
<ng-container *ngIf="columnMeta[cell.id] && columnMeta[cell.id].tableCell" [ngTemplateOutlet]="columnMeta[cell.id].tableCell" [ngTemplateOutletContext]="{content: row[cell.id]}"></ng-container>
<ng-container *ngIf="!columnMeta[cell.id]">{{row[cell.id] || ''}}</ng-container>
</td>
</tr>
Finally, to use it I just do the following:
<ui-table *ngIf="tableDataModel.rows?.length>0" [model]="tableDataModel" (onRequestEdit)="onOpenEdit($event)">
<ui-table-cell match="permissions">
<ng-template #tableCell let-content="content">
{{content|arrayToList}}
</ng-template>
</ui-table-cell>
</ui-table>

how to read the array of data in html templates

I have three variables and i was created one array and pushed all these three variables in to this array.
And in my html template i am using a table. I tried with *ngFor but it is not working and also i tried with string interpolation and that too not woriking.
I am getting an error called <--can not read property "title" of undefined-->
In my console i am getting data like this....
array of data
(3)[{...}{...}{...}]
0:{key:"-LtZprPfMtDgzuxmaI5o"}
1:{price:1}
2:{title:"title1"}
array of data
(3)[{...}{...}{...}]
0:{key:"-LtcY8_6dd8fzibK9EYV"}
1:{price:2}
2:{title:"title2"}
array of data
(3)[{...}{...}{...}]
0:{key:"-LtcZF9NHknDZ0OcKzHg"}
1:{price:3}
2:{title:"title3"}
And here my product.component.ts
import { ProductsService } from './../../products.service';
import { Component, OnInit } from '#angular/core';
import { AngularFireDatabase, AngularFireAction, DatabaseSnapshot } from 'angularfire2/database';
#Component({
selector: 'app-admin-products',
templateUrl: './admin-products.component.html',
styleUrls: ['./admin-products.component.css']
})
export class AdminProductsComponent{
constructor(
private products:ProductsService,
private db : AngularFireDatabase
) {
products.getAll().on('child_added',function(c){
let data = c.val();
let key = c.key;
let price = data.price;
let title = data.title;
let array=[];
array.push({"key":key});
array.push({"title":title});
array.push({"price":price});
console.log('array of data',array);
})
}
}
And my admin-products.component.html
<table class="table">
<thead>
<th>Title</th>
<th>Price</th>
<th></th>
</thead>
<tbody *ngFor="let p of array">
<td>{{p.title}}</td>
<td>{{p.price}}</td>
<td ><a [routerLink]="['/admin/products',p.key]">Edit</a></td>
</tbody>
</table>
From your html code, I think you need a array of object.
for example :
array = [
{key: 'key1', title: 'title 1', price: 10, },
{key: 'key2', title: 'title 2', price: 10, },
{key: 'key3', title: 'title 3', price: 10, }
]
if my assumption is correct then there are several problems here.
array construction is not ok.
your array variable is local. make it a public variable otherwise from html you can't access it.
In html, you are trying to iterate the array and each iteration, the data will be assigned in your local variable 'p' (as you are using *ngFor="let p of array")
so, change your code to this
import { ProductsService } from './../../products.service';
import { Component, OnInit } from '#angular/core';
import { AngularFireDatabase, AngularFireAction, DatabaseSnapshot } from 'angularfire2/database';
#Component({
selector: 'app-admin-products',
templateUrl: './admin-products.component.html',
styleUrls: ['./admin-products.component.css']
})
export class AdminProductsComponent{
array: any = [];
constructor(
private products:ProductsService,
private db : AngularFireDatabase
) {
products.getAll().on('child_added',function(c){
let data = c.val();
let key = c.key;
let price = data.price;
let title = data.title;
this.array.push({"key":key, "title":title, "price":price });
console.log('array of data', this.array);
})
}
}
and change your html to this,
<table class="table">
<thead>
<th>Title</th>
<th>Price</th>
<th></th>
</thead>
<tbody *ngFor="let p of array">
<td>{{p.title}}</td>
<td>{{p.price}}</td>
<td ><a [routerLink]="['/admin/products',p.key]">Edit</a></td>
</tbody>
</table>
Change this:
<tbody *ngFor="let p of array">
<td>{{array.title}}</td>
<td>{{array.price}}</td>
into this:
<tbody *ngFor="let p of array">
<td>{{p.title}}</td>
<td>{{p.price}}</td>
You have an array of object, therefore you need to use ngFor to iterate inside the array and then use the variable p to access the attribute's values.
Try like this:
Have the loop in tr instead of td
Change {{array.title}} to {{p.title}}
Make array global
.html
<tbody>
<tr *ngFor="let p of array">
<td>{{p.title}}</td>
<td>{{p.price}}</td>
<td ><a [routerLink]="['/admin/products',p.key]">Edit</a></td>
</tr>
</tbody>
.ts
export class AdminProductsComponent{
array:any[] = [];
products.getAll().on('child_added',function(c){
let data = c.val();
let key = c.key;
let price = data.price;
let title = data.title;
this.array=[];
this.array.push({"key":key});
this.array.push({"title":title});
this.array.push({"price":price});
console.log('array of data',this.array);
})
}
array is a local variable inside your callback. Make it a member of your component to make it available in the template.
export class AdminProductsComponent{
array: any[] = [];
constructor(
private products:ProductsService,
private db : AngularFireDatabase
) {
products.getAll().on('child_added', c => {
let data = c.val();
let key = c.key;
let price = data.price;
let title = data.title;
// you probably want one item per product, not three??
this.array.push({ key, title, price });
})
}
}
Template:
<tbody >
<tr *ngFor="let p of array">
<td>{{p.title}}</td>
</tr>
</tbody>
Define an array property in your component and pass objects into it.
Use Arrow function for the on callback so your this context is the component.
import { ProductsService } from './../../products.service';
import { Component, OnInit } from '#angular/core';
import { AngularFireDatabase, AngularFireAction, DatabaseSnapshot } from 'angularfire2/database';
#Component({
selector: 'app-admin-products',
templateUrl: './admin-products.component.html',
styleUrls: ['./admin-products.component.css']
})
export class AdminProductsComponent{
// Define property here
public array = [],
constructor(
private products:ProductsService,
private db : AngularFireDatabase
) {
products.getAll().on('child_added',(c) => {
let data = c.val();
let key = c.key;
let price = data.price;
let title = data.title;
this.array.push({key, title, price});
})
}
}

Using an ngFor to traverse a 2 dimensional array

I've been beating my head up against the wall on this one for a while but I finally feel close. What I'm trying to do is read my test data, which goes to a two dimensional array, and print its contents to a table in the html, but I can't figure out how to use an ngfor to loop though that dataset
Here is my typescript file
import { Component } from '#angular/core';
import { Http } from '#angular/http';
#Component({
selector: 'fetchdata',
template: require('./fetchdata.component.html')
})
export class FetchDataComponent {
public tableData: any[][];
constructor(http: Http) {
http.get('/api/SampleData/DatatableData').subscribe(result => {
//This is test data only, could dynamically change
var arr = [
{ ID: 1, Name: "foo", Email: "foo#foo.com" },
{ ID: 2, Name: "bar", Email: "bar#bar.com" },
{ ID: 3, Name: "bar", Email: "bar#bar.com" }
]
var res = arr.map(function (obj) {
return Object.keys(obj).map(function (key) {
return obj[key];
});
});
this.tableData = res;
console.log("Table Data")
console.log(this.tableData)
});
}
}
Here is my html which does not work at the moment
<p *ngIf="!tableData"><em>Loading...</em></p>
<table class='table' *ngIf="tableData">
<tbody>
<tr *ngFor="let data of tableData; let i = index">
<td>
{{ tableData[data][i] }}
</td>
</tr>
</tbody>
</table>
Here is the output from my console.log(this.tableData)
My goal is to have it formatted like this in the table
1 | foo | bar#foo.com
2 | bar | foo#bar.com
Preferably I'd like to not use a model or an interface because the data is dynamic, it could change at any time. Does anyone know how to use the ngfor to loop through a two dimensional array and print its contents in the table?
Like Marco Luzzara said, you have to use another *ngFor for the nested arrays.
I answer this just to give you a code example:
<table class='table' *ngIf="tableData">
<tbody>
<tr *ngFor="let data of tableData; let i = index">
<td *ngFor="let cell of data">
{{ cell }}
</td>
</tr>
</tbody>
</table>

Referencing objects in HTML in Angular2

I am fetching data from an api server and storing it as a object in TypeScript, I need help referencing that data in HTML as a key and value pair.
TypeScript:
if (x.AttributeTemplateId==templateId) {
x['AttributeTemplateValue'].forEach(function(x){
console.log('hello');
if (x.AttributeTemplateId==templateId){
console.log(templateId);
(function(y){
console.log("hello2");
newName.push(y.CodeSet);
newValue.push(y.CodeValue);
// console.log([newAttributes]);
})(x);
}
})
}
})
newName = this.name;
newValue = this.value;
this.attributes = this.name.reduce((acc, value, i) => (acc[value] = this.value[i], acc), {});
console.log(this.attributes);
}
My data is in this.attributes and it looks like this
I want to put key and value in a table like
<table>
<tr>
<th> Name </th>
<th> Value </th>
</tr>
<tr key>
<td value > </td>
</tr>-->
</table>
How would I achieve this in Angular2 ?
step 1: Create a custom pipe that stores all the object keys and values in an array and returns them just like this
import { PipeTransform, Pipe} from '#angular/core';
#Pipe({name: objKeys})
export calss objKeysPipe implements PipeTransform {
transform(value, arguments: string[]) :any {
let keys= [];
for (let k in value){
keys.push({key: k, value: value[k]});
}
return keys;
}
setp 2: in your component template you do the folowing
<table>
<tr *ngFor="let att of attributes | objKeys">
<th>{{att.key}}</th>
<th>{{att.value}}</th>
</tr>
</table>

Anuglar2- model data jumps

I'm making a Anuglar2 application for people to log how many hours they put into each course, assignments, etc., per week (though there we be more advance options later on).
Right now i have a table which lists out how many hours you spent on each course per day. I want the user to be able to just edit and change any values as he/she goes along. So i have a two dimensional array ( named data), and i attach models to each element in the array, which i then represent as a input element.
Everything works fine, but there is a weird bug. Whenever you delete the value in the input box and re-enter new data, it jumps to the next input element. I cant figure out why. Anyone got any ideas ?
Example in GIF format (sorry for the quality had to use a converter)
gif link in case you cant see it on Stack
home.html
<!-- Table -->
<div class="row">
<div class="col-lg-12 col-sm-12">
<h1 class="page-header">Weekly Status Report</h1>
<table class="table table-responsive table-stripped table-bordered table-hover" *ngIf="courses">
<thead>
<tr class="btn-primary">
<th>Course</th>
<th>Time</th>
<th>SM #</th>
<th>Est</th>
<th *ngFor="let weekday of week">
{{weekday | date:'EEEE'}}
</th>
<th>Total</th>
</tr>
</thead>
<tbody *ngFor="let course of courses; let i = index;">
<tr>
<td >
{{course.name}}
</td>
</tr>
<tr class="alert-info">
<!-- Account for the title row -->
<td>
Date
</td>
<td >
Documentation Type
</td>
<td>
Documentation Text
</td>
</tr>
<tr *ngFor="let content of course.hours" class="alert-warning">
<td>
{{content.day| date}}
</td>
<td [attr.colspan]="3">
{{ title }}
</td>
<td [attr.colspan]="7">
</td>
</tr>
<tr class="alert-success">
<td></td>
<td></td>
<td></td>
<th></th>
<!-- DATA ARRAY -->
<th *ngFor="let d of data[i]; let j = index;">
<div>
<input maxlength="3" size="3" [(ngModel)]="data[i][j]" />
</div>
</th>
<td></td>
</tr>
</tbody>
<tfoot class="btn-primary">
<tr>
<td>Report Totals(all courses)</td>
<td></td>
<td></td>
<td></td>
<td *ngFor="let pivot of [0,1,2,3,4,5,6]">
{{ getSum(pivot) }}
</td>
<td>
{{getTotal()}}
</td>
</tr>
</tfoot>
</table>
</div>
</div>
home.component.ts
week: Array<any> = new Array();
data: Array<any> = new Array();
constructor(private hm: HomeService) {
let subscription = this.hm.getFormat(this.courses, this.week)
.subscribe(
value => {
this.data.push(value);
},
error => {
console.log(error)
},
() => {
console.log("Formated");
}
);
}
home.service.ts
getFormat(courses: any, weekdays: any): Observable<any> {
return new Observable(observer => {
// For all courses
courses.forEach(c => {
// temp week
let week: Array<any> = new Array();
// For each weekday
weekdays.forEach(w => {
let found: boolean = false;
// get hours spent on course
c.hours.forEach(h => {
let hour:Date = new Date (h.day);
// if the hours spent match up on the day push it to the week array
if (w.day.getTime() === hour.getTime()) {
week.push(h.duration);
found = true
}
});
// If no time was found on this take, push a empty string.
if (!found) {
week.push(0);
}
});
// push that week to the component
observer.next(week);
});
observer.complete();
});
}
This is happening because of how data is tracked.
When you change the value from the array it will become another value, thus not being able to track it as it will keep track of the older value.
Your case is similar to the following bad code:
#Component({
selector: 'my-app',
template: `
<div>
<table>
<tr *ngFor="let dim1 of values; let dim1Index = index">
<td *ngFor="let dim2 of dim1; let dim2Index = index">
<input type="text" [(ngModel)]="values[dim1Index][dim2Index]" />
</td>
</tr>
</table>
</div>
`,
})
export class App {
values: string[][];
constructor() {
this.values = [
['a1', 'b1', 'c1'],
['a2', 'b2', 'c2'],
['a3', 'b3', 'c3']
];
}
}
There are multiple solutions:
Solution 1: Using an object to wrap the value
Look at the values array.
#Component({
selector: 'my-app',
template: `
<div>
<table>
<tr *ngFor="let dim1 of values; let dim1Index = index">
<td *ngFor="let dim2 of dim1; let dim2Index = index">
<input type="text" [(ngModel)]="values[dim1Index][dim2Index].value" />
</td>
</tr>
</table>
<br/>
{{ values | json }}
</div>
`,
})
export class App {
values: string[][];
constructor() {
this.values = [
[{ value: 'a1' }, { value: 'b1' }, { value: 'c1' }],
[{ value: 'a2' }, { value: 'b2' }, { value: 'c2' }],
[{ value: 'a3' }, { value: 'b3' }, { value: 'c3' }]
];
}
}
Solution 2: Using a custom trackBy function for *ngFor
Note that this solution is based on trackByIndex function which returns the index of the item, thus the item being located by its index instead of its value.
#Component({
selector: 'my-app',
template: `
<div>
<table>
<tr *ngFor="let dim1 of values; let dim1Index = index; trackBy: trackByIndex">
<td *ngFor="let dim2 of dim1; let dim2Index = index; trackBy: trackByIndex">
<input type="text" [(ngModel)]="values[dim1Index][dim2Index]" />
</td>
</tr>
</table>
<br/>
{{ values | json }}
</div>
`,
})
export class App {
values: string[][];
constructor() {
this.values = [
['a1', 'b1', 'c1'],
['a2', 'b2', 'c2'],
['a3', 'b3', 'c3']
];
}
public trackByIndex(index: number, item) {
return index;
}
}
Therefore in your code you can use:
<tbody *ngFor="let course of courses; let i = index; trackBy: trackByIndex">
next
<th *ngFor="let d of data[i]; let j = index; trackBy: trackByIndex">
and finally define trackByIndex in your component:
public trackByIndex(index: number, item) {
return index;
}

Categories

Resources