How to feed data to mat-cell based off of data from a different mat-cell - angular5

I have a mat-table that renders data dynamically like this:
<ng-container matColumnDef="type">
<mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.type}} </mat-cell>
</ng-container>
<ng-container matColumnDef="count">
<mat-header-cell *matHeaderCellDef> Count </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.count}} </mat-cell>
</ng-container>
I dont have count in the model i am querying the number of items from the back end and returning the count by grouping how many each type has using aggregate function and $group. the types (electronics, households...) are in the collection
..
[{_id: objectId type: electronics}
{_id: objectId type: households}
]
after aggregate and grouping query i get this array back
[
{type: electronics count: 139},
{type: households count: 400},
...
]
I want to add a count column that counts the number of different items where electronics will be its own row and return 139 in the count column and household its own row and return 400
I am successfully in logging the data returned from the backend but how do i display it on mat table.
Using angular 5 material how do I iterate through the mat-cell and grab each type (electronic, households from a different cell) and display the count for that type on its respective row? any suggestion would be appreciated!

If you have this array on your component and want to show this on table.
let mydata = [
{type: electronics count: 139},
{type: households count: 400},
...
]
and in your template you have
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="items">
<mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.type}} </mat-cell>
</ng-container>
<ng-container matColumnDef="count">
<mat-header-cell *matHeaderCellDef> Count </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.count}} </mat-cell>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: ['type','count'];">
</tr>
</table>

you can save your data returning into your component and then you can write a function to get a count of given item from the component like.
component
import { Component } from '#angular/core';
#Component({
templateUrl: './demo.component.html',
styleUrls: ['./demo.component.css']
})
export class DemoComponent {
result : any[];
constructor() { }
getCount(itemName){
let count = 0;
for(var i = 0; i < result.length; i++){
if(result[i].itemName===itemName){
count++;
}
}
return count;
}
}
on Table you can have
<ng-container matColumnDef="items">
<mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.items}} </mat-cell>
</ng-container>
<ng-container matColumnDef="count">
<mat-header-cell *matHeaderCellDef> Count </mat-header-cell>
<mat-cell *matCellDef="let element"> {{getCount(element.itemName)}} </mat-cell>
</ng-container>

ok. you have two different arrays
export interface myData {
elements: any[];
}
#Component({
templateUrl: './demo.component.html',
styleUrls: ['./demo.component.css']
})
export class DemoComponent {
displayedColumns: string[] = ['type','count'];
dataSource : myData[] = [];
arrayThatcontainsCount = [];
someFunction(){
this.someService.get().then((result: any) =>{
this.dataSource = result.data;
}
}
someFunction(){
//getting count array from api and setting array "arrayThatcontainsCount"
}
getCountOfElement(type){
let elem = this.arrayThatcontainsCount.find((r)=> r.type == type);
return elem.count;
}
}
and in html
<ng-container matColumnDef="items">
<mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.type}} </mat-cell>
</ng-container>
<ng-container matColumnDef="count">
<mat-header-cell *matHeaderCellDef> Count </mat-header-cell>
<mat-cell *matCellDef="let element"> {{getCountOfElement(element.type)}} </mat-cell>
</ng-container>

Related

Implementing column specific filters on dynamically created angular material data table

I am generating a material data table dynamically with data as well as displayedColumns coming from backend.
.html:
<mat-form-field appearance="standard">
<mat-label>Filter</mat-label>
<input matInput (keyup)="applyFilter($event)" placeholder="Ex. Mia" #input>
</mat-form-field>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<ng-container *ngFor="let column of displayedColumns">
<ng-container [matColumnDef]="column">
<th mat-header-cell *matHeaderCellDef > {{ column }} </th>
<td mat-cell *matCellDef="let row">{{row[column]}}</td>
</ng-container>
</ng-container>
</table>
<mat-paginator [pageSizeOptions]="[10, 5, 25, 100]" aria-label="Select page of users"></mat-paginator>
</div>
If it was not a dynamic table, I can add an input field in html for column filters and write code in .ts to use filterPredicate property to customize the logic for column specific filtering.
.ts code excerpts:
....
this.dataSource.filterPredicate = this.createFilter();
...
...
createFilter(): (data: any, filter: string) => boolean {
let filterFunction = function(data, filter): boolean {
let searchTerms = JSON.parse(filter);
let descr:string = data.description??"";
return (data.view_name.toLowerCase().indexOf(searchTerms.view_name) !== -1)
&& (descr.toString().toLowerCase().indexOf(searchTerms.description) !== -1);
However, the table being dynamic I have 2 questions:
if I add column filter in html, how do I identify for which column user entered text inside .ts?
Even if I identify the column name and say I got it into a variable, how do I write the filtering logic that is equivalent to above since I can't refer to searchTerms.view_name directly?

Expanding in Angular Material

I am using Angular Material for displaying content.
My TS code is:
import { Component, OnInit, ViewChild } from '#angular/core';
import { AdminReassignTaskService } from 'src/app/services/admin-reassign-task.service'
import { Location } from '#angular/common';
import { trigger, state, transition, animate, style } from '#angular/animations';
#Component({
selector: 'app-admin-reassign-task',
templateUrl: './admin-reassign-task.component.html',
animations: [
trigger('detailExpand', [
state('collapsed', style({height: '0px', minHeight: '0', display: 'none'})),
state('expanded', style({height: '*'})),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]),
],
styleUrls: ['./admin-reassign-task.component.scss']
})
export class AdminReassignTaskComponent implements OnInit {
reassignedlist;
columnsToDisplay = ['test'];
#ViewChild('expandedElement') expandedElement;
displayedColumns = ['comment'];
taskList;
constructor(private serv: AdminReassignTaskService,public _location: Location) { }
ngOnInit() {
this.serv.getByURL('admin/list').subscribe(response => {
this.reassignedlist=response;
})
}
editReassigned(i,element){
const result = [this.reassignedlist.find( ({ id }) => id === i )];
this.taskList=result;
this.expandedElement = this.expandedElement === element ? null : element ;
}
}
And my HTML Code is
<div class="main-content-wraper">
<ng-container>
<div class="mat-elevation-z2 card rounded-0 p-3 d-flex flex-row flex-wrap" >
<table mat-table [dataSource]="reassignedlist" multiTemplateDataRows class="mat-elevation-z8 w-100 custom-table">
<ng-container matColumnDef="task_name">
<th mat-header-cell *matHeaderCellDef> Task Name </th>
<td mat-cell *matCellDef="let element"> {{element.taskName}} </td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button matTooltip="View Task" (click)="editReassigned(element.id,element)" ><mat-icon >flag</mat-icon></button>
</td>
</ng-container>
<ng-container matColumnDef="expandedDetail">
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplay.length">
<div class="example-element-detail" [#detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
<table mat-table [dataSource]="taskList" class="task-card" >
<ng-container matColumnDef="comment">
<th mat-header-cell *matHeaderCellDef> Assigned User Comment</th>
<td mat-cell *matCellDef="let element" matTooltip="Assigned User Comment"> {{element.assignedUserCmt}} </td>
</ng-container>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let element; columns: columnsToDisplay;"
class="example-element-row"
[class.example-expanded-row]="expandedElement === element" >
</tr>
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table>
</div>
</ng-container>
</div>
By this code, Main Table and Onclicking ICON in the main table Second table expands and I can View Data. But,
1) 2nd Table not Displaying Heading to it. What is the Wrong Coding Here?
2) After 2 Table I wanted to add some 4-5 Input Field in this Expand and collapse manner. How I can ADD without a table?
3) Here Attached Image (Output of current Coding). Kindly let me know anyone having an answer with you.
I think you need to be using expansion panels as part of angular material accordion:
https://stackblitz.com/angular/dnbkkoraexxa?file=src%2Fapp%2Fexpansion-overview-example.ts

Angular 5 onchange input text field in mat-table

component file
export interface Assess {
ques: string;
unit: string;
}
const DATA: Assess[] = [
{ ques: 'Release volume', unit: '' },
{ ques: 'Sprint Cycle ', unit: 'week(s)' },
{ ques: 'No. of stories', unit: '/spr' }
];
export class AssessComponent implements OnInit {
/** display questions */
displayedColumns: string[] = ['ques', 'unit'];
dataSource = new MatTableDataSource(DATA);
userMetrics(statusValue: any): void {
console.log("*** NEW VALUE :: -->> "+JSON.stringify(statusValue));
}
}
and HTML file
<mat-table #table [dataSource]="dataSource">
<ng-container matColumnDef="ques">
<mat-cell *matCellDef="let element"> <label> {{element.ques}} </label> </mat-cell>
</ng-container>
<ng-container matColumnDef="unit">
<mat-cell *matCellDef="let element; let index=index">
<mat-form-field>
<input type="text" matInput #item name="{{'metricsInput'+index}}"
[(ngModel)]="userInput.name" (change)="userMetrics(item.value)">
<!-- <input type="text" matInput placeholder="Metric Value"> -->
</mat-form-field> {{element.unit}}
</mat-cell>
</ng-container>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
Input fileds in table
Onchange when the user enters the new value to textbox,
question selected and answer provided from each mat-cell of the table should be shown in console in the function userMetrics()
Please help me since I'm new to Angular
Finally, I found the solution
<ng-container matColumnDef="unit">
<mat-cell *matCellDef="let element">
<mat-form-field>
<input type="text" matInput placeholder="User Value"
(input)="userMetrics(element.ques,$event.target.value)">
</mat-form-field> {{element.unit}}
</mat-cell>
</ng-container>

Trouble using renderRows in Mat-Table

I'm trying to update my table in angular 8, when data is added using renderRows() method but it is not working.
I have used #ViewChild and .renderRows() method after getting response also can anyone explain the meaning of this : {static: true}
//TODO: allocateMoney.ts
#ViewChild(MatTable, { static: true }) table: MatTable<any>;
openDialog() {
let dialogRef = this.dialog.open(AllocateMoneyFormComponent, {
disableClose: true,
data: {
projectId: this.projectId,
budgetId: this.budgetId
}
});
dialogRef.afterClosed().subscribe((result) => {
if (result == 'loadData') {
this.table.renderRows();
}});
}
//TODO: allocateMoney.html
<table class="example-list" mat-table [dataSource]="budget_dm.budget[budgetId].allocateMoney.tables.rows"
class="mat-elevation-z8">
<ng-container matColumnDef="id">
<th mat-header-cell [style.display]="'none'" *matHeaderCellDef> Id </th>
<td mat-cell [style.display]="'none'" *matCellDef="let element">{{element.id}}
</td>
</ng-container>
<ng-container matColumnDef="categoryCode">
<th mat-header-cell *matHeaderCellDef style="padding-left: 15px;"> Acc </th>
<td mat-cell *matCellDef="let element">{{element.categoryCode}}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="budget_dm.budget[budgetId].allocateMoney.tables.cols"></tr>
<tr class="example-box" mat-row
*matRowDef="let row; columns: budget_dm.budget[budgetId].allocateMoney.tables.cols;"></tr>
</table>
//TODO: allocateMoneyForm.ts
isSubmitForm() {
this.formdata.raw.projectId = this.projectId;
this.formdata.raw.budgetId = this.budgetId;
this.budgetService.allocateMoney(this.orgId, this.formdata.raw).then(resp => {
if (resp) {
this.toast_.successToastr("Money Allocated Success", "SUCCESS");
} else {
this.toast_.errorToastr("Money Allocated Failure", "ERROR");
}
});
this.dialogRef.close('loadData');
}
core.js:5847 ERROR TypeError: Cannot read property 'renderRows' of undefined
at SafeSubscriber._next (allocate-money.component.ts:57)
at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub (Subscriber.js:192)
at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next (Subscriber.js:130)
at Subscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next (Subscriber.js:76)
at Subscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:53)
at Subject.push../node_modules/rxjs/_esm5/internal/Subject.js.Subject.next (Subject.js:47)
at SafeSubscriber._next (dialog.es5.js:429)
at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub (Subscriber.js:192)
at SafeSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next (Subscriber.js:130)
at Subscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next (Subscriber.js:76)
Try adding the #table reference to your html mat table , this is to make sure your mat table childview is well instantiated

Can datatables sort a column with an input field?

I am trying to make datatables sort my columns. The first column works okay as it's a simple number. However the next column is an input field. When I try to make that sort then nothing happens.
<table width="100%" cellspacing="0" class="table sortable no-margin">
<thead>
<tr>
<th scope="col" class="sorting" style="width: 57px;">
<span class="column-sort">
</span>
ID
</th>
<th scope="col" class="sorting_desc" style="width: 94px;">
<span class="column-sort">
</span>
Order
</th>
</tr>
</thead>
<tbody>
<tr id="row_20" class="odd">
<td id="refKey_20" style="text-align:center;" class="">
1Y
</td>
<td class=" sorting_1">
<input type="text" value="160" size="3" name="item.Order"
maxlength="3" id="Order_20" >
</td>
</tr>
<tr id="row_19" class="even">
<td id="refKey_19" style="text-align:center;" class="">
1X
</td>
<td class=" sorting_1">
<input type="text" value="150" size="3" name="item.Order"
maxlength="3" id="Order_19" >
</td>
</tr>
</tbody>
</table>
Is there some way that I can get datatables to sort input fields?
The easiest way is to add a hidden span inside columns <span style="visibility:hidden">value of the input</span>
You should look at this example that explains how to do sorting on input fields. Basically you declare a sorting function
/* Create an array with the values of all the input boxes in a column */
$.fn.dataTableExt.afnSortData['dom-text'] = function ( oSettings, iColumn )
{
var aData = [];
$( 'td:eq('+iColumn+') input', oSettings.oApi._fnGetTrNodes(oSettings) ).each( function () {
aData.push( this.value );
} );
return aData;
}
And then tell to your table to use that
$('#example').dataTable( {
"aoColumns": [
null,
{ "sSortDataType": "dom-text" }
]
} );
or wit aoColumnDefs
$('#example').dataTable( {
"aoColumnDefs": [{ "sSortDataType": "dom-text" , aTarget: "yourclass"}]
} );
For versions of Datatables 1.10+ the names of some option variables have been changed and a new API introduced. Documentation here: http://datatables.net/examples/plug-ins/dom_sort.html.
Here is a working version of the above accepted answer in 1.10+:
$(document).ready(function () {
var table = $('#example').DataTable({
"columnDefs": [
{
"orderDataType": "dom-input",
"targets": 0, // Just the first column
},
],
});
});
The custom sort function:
$.fn.dataTable.ext.order['dom-input'] = function (settings, col) {
return this.api().column(col, { order: 'index' }).nodes().map(function (td, i) {
return $('input', td).val();
});
}
jQuery.fn.dataTableExt.oSort['textbox-asc'] = function (a, b) {
var vala = $('#' + $(a).filter('input').attr('id')).val().toLowerCase();
var valb = $('#' + $(b).filter('input').attr('id')).val().toLowerCase();
if (vala === '')
return 1;
if (valb === '')
return -1;
return vala < valb ? -1 : vala > valb ? 1 : 0;
};
jQuery.fn.dataTableExt.oSort['textbox-desc'] = function (a, b) {
var vala = $('#' + $(a).filter('input').attr('id')).val().toLowerCase();
var valb = $('#' + $(b).filter('input').attr('id')).val().toLowerCase();
if (vala === '')
return 1;
if (valb === '')
return -1;
return vala < valb ? 1 : vala > valb ? -1 : 0;
};
then use it like this
$(datatable).dataTable({
"iDisplayLength": 50,
"bLengthChange": false,
"bPaginate": false,
"columns": [
null, { "sType": "textbox" }
],
});
If you decide to use the columns option where you are rending information from a JSON file you can easily add a hidden span on your render property. It appears as though DataTables looks for text to order and if it cannot find any, it will break. The example at https://datatables.net/examples/plug-ins/dom_sort.html uses a table that has already been rendered. Here is an example using an API:
...columns([{
"data": "receivedDate",
"render": function (data, type, row, meta)
{
if (data == "null")
{
return "<input type='text' id='datepicker_" + meta.row + "' class='datepicker form-control' /><span class='hidden'><span>";
}
else
{
return "<input type='text' id='datepicker_" + meta.row + "' class='datepicker form-control' value='" + moment(data).format("MM/DD/YYYY") + "'/><span class='hidden'>" + moment(data).format('MM/ DD / YYYY') + "</span>";
}
}
}]);
Set an invisible div with the value before the input field.
<tbody>
<tr id="row_20" class="odd">
<td id="refKey_20" style="text-align:center;" class="">
1Y
</td>
<td class=" sorting_1">
<div style="display:none;">160</div>
<input type="text" value="160" size="3" name="item.Order"
maxlength="3" id="Order_20" >
</td>
</tr>
<tr id="row_19" class="even">
<td id="refKey_19" style="text-align:center;" class="">
1X
</td>
<td class=" sorting_1">
<div style="display:none;">150</div>
<input type="text" value="150" size="3" name="item.Order"
maxlength="3" id="Order_19" >
</td>
</tr>
</tbody>