Setting Angular Material mat-table column width in column configuration - html

I have the following column configuration for my Angular Material mat-table columns:
export interface TableColumn {
name: string;
dataKey: string;
isSortable?: boolean;
width?: string; // column width
}
//...
this.tableColumns = [
{
name: 'Id',
dataKey: 'id',
position: 'left',
width: '50px'
},
{
name: 'Name',
dataKey: 'name',
position: 'left',
width: '100%'
}
];
As far as I see, many people use css in order to set column width as on this page, etc. However, would not it be nice if we set the column widths for each column by pixel and percentage in the this.tableColumns array as the other properties when defining columns? I think I just need a configuration of mat-table, but I am not sure if there is such a config for mat-table. If not, I think I can use [style.width] and set each column by column config value. Any suggestion for that?

In general, you defined the columns based in your array like:
<ng-container *ngFor="let column of tableColumns;let first=first">
<ng-container [matColumnDef]="column.dataKey">
<th [style.width]="column.width" mat-header-cell *matHeaderCellDef>
{{column.name}}
</th>
<td [style.width]="column.width" mat-cell
*matCellDef="let element"> {{element[column.dataKey]}}
</td>
</ng-container>
</ng-container>
And your displayedColumns as
this.displayedColumns=this.tableColumns.map(x=>x.dataKey)
NOTE: I supose you defined the width as '50%' or '30px' if only use a number you can use, e.g.
[style.width]="column.width+'px'"
Update imagine you want to create a column with actions buttons, but this buttons can be one, two or three. We can asign to this last colum a width of "1%" and 'white-space: nowrap'
//we has two variables
btDelete:boolean=true;
btEdit:boolean:true;
<ng-container matColumnDef="action">
<th style="width:1%" mat-header-cell *matHeaderCellDef>
</th>
<td style="width:1%;white-space: nowrap;" mat-cell
*matCellDef="let element">
<button *ngIf="btEdit" mat-button (click)="edit(element)">Edit</button>
<button *ngIf="btDelete" mat-button (click)="delete(element)">Delete</button>
</td>
</ng-container>
NOTE: I'm not prety sure what happens with the width of columns because the % total is bigger that 100%
We can try using the mat-table in flex-mode
<ng-container *ngFor="let column of columns">
<ng-container [matColumnDef]="column.name">
<mat-header-cell [style.flex]="column.width" *matHeaderCellDef> {{column.width}}</mat-header-cell>
<mat-cell [style.flex]="column.width" *matCellDef="let element"> {{element[column.name]}} </mat-cell>
</ng-container>
</ng-container>
<ng-container matColumnDef="action">
<mat-header-cell style="flex:0 1 auto;visibility:hidden" *matHeaderCellDef>
<button *ngIf="btEdit" mat-button>Delete</button>
<button *ngIf="btDelete" mat-button>Delete</button>
</mat-header-cell>
<mat-cell style="flex:0 1 auto" *matCellDef="let element"> <button *ngIf="btEdit" mat-button>Edit</button>
<button *ngIf="btDelete" mat-button>Delete</button></mat-cell>
</ng-container>
See that the column "action" the "head" repeat the buttons -with visibility:hidden-
NOTE: In this case, the "with" -really the flex- is a number, not a %,

While there isn't a way to define column width in the array, you should be able to define it using ng-style. It is possible to provide column specific properties in the html template, therefore this too should work. An example would be
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<mat-text-column name="position" [headerText]="headerText" [ngStyle]="{'width':'20%'}"></mat-text-column>
<!-- Change the header text. -->
<mat-text-column name="name" headerText="Element" [ngStyle]="{'width':'40%'}"></mat-text-column>
<!-- Provide a data accessor for getting the cell text values. -->
<mat-text-column name="weight" [dataAccessor]="getWeight" [ngStyle]="{'width':'40%'}"></mat-text-column>
</table>
The result of style.width can also be accomplished by ngStyle, so that too should work in the same way.

Related

HTML Angular Material: Hide Columns in a dynamically forming table

I want to hide columns in my table based on an ID value that is sent to the page upon opening it. I've seen there is a method for tables whose columns are formed as needed... https://github.com/angular/components/issues/9940 this post outlines that. However my table is formed as such.
<table mat-table [dataSource]="dataSource" align="center" class="mat-elevation-z8">
<ng-container [matColumnDef]="column" *ngFor="let column of getDisplayedColumns()">
<th mat-header-cell *matHeaderCellDef > {{column}} </th>
<td mat-cell *matCellDef="let element" > {{element[column]}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns" ></tr>
<tr mat-row *matRowDef="let row; columns: getDisplayedColumns();" ></tr>
</table>
getDisplayedColumns() should return my array with only the elements I want to show
I'm confused on how I would implement the above method into my code given that my table is formed differently than the OP's.
Probably a lot of ways to do this, but I had a similar need so created a component and passed in all possible columns. The component does a lot of special formatting like booleans = "yes" | "no" or dates dates | date:'MM/dd/yyyy h:mm:ss a', but for your case isVisible is what you seem to need. A few key pieces:
export interface IPaginateRowAttributes {
attributeName: string;
listLabel: string;
listFormat: string;
isAttribute: boolean;
lookupIndex: number;
isVisible: boolean;
}
#Input() rowColumns: IPaginateRowAttributes [] = [];
#Input() yourId
<tr *ngFor="let dynamicListRowAttribute of dynamicListRowAttributes">
<ng-container *ngIf="dynamicListRowAttribute.listFormat=='number' && dynamicListRowAttribute.isVisible==true" matColumnDef={{dynamicListRowAttribute.compositeKey}}>
<th mat-header-cell *matHeaderCellDef mat-sort-header (click)="sort(dynamicListRowAttribute.attributeName, true, dynamicListRowAttribute.listFormat)"> {{dynamicListRowAttribute.listLabel}} </th>
<td mat-cell *matCellDef="let element"> {{element['attributes'][dynamicListRowAttribute.attributeName] | currency}} </td>
</ng-container>
<ng-container> more format options</ng-container>
ngOnChanges(changes: SimpleChanges) {
let returnHeaders: string[] = [];
for (let detail of displayColumns) {
returnHeaders.push(detail.dictionaryCompositeKey);
}
}
So right there include ngOnChanges, you could check your ID value and set isVisible per column based on whatever criteria you want to use and let the *ngIf filter the rest. This is one option anyway, there may be better solutions in terms of performance.

Trying to loop through elements and set styles at same time

I have an angular material table
(HTML)
<table mat-table
[dataSource]="dataSource" multiTemplateDataRows
class="mat-elevation-z8">
<ng-container *ngIf="{{column}}" matColumnDef="{{column}}" *ngFor="let column of displayedColumns">
<th mat-header-cell *matHeaderCellDef> {{column}} </th>
<td mat-cell *matCellDef="let element"> {{element[column]}} </td>
</ng-container>
I want to change the styling on the element if it equals 'Missing' (e.g. {{element[column}} == Missing then change styling).
(style) (HTML)
<mat-chip *ngIf="element.status=='Missing'" style="background-color: #ec9d9d; color: red">Missing</mat-chip>
How can I do this in the HTML? I only want to do this if the displayedColumn 'status' is equal to 'Missing'.
(Typescript)
displayedColumns: string[] = [
'id',
'tradingPartnerTradingPartner',
'fileFormatFileFormat',
'status',
];
you could use the ng-class directive
in your example, using it should be something like this:
(Assuming the mat-chip would get placed into this cell I modified below)
<table mat-table [dataSource]="dataSource" multiTemplateDataRows
class="mat-elevation-z8">
<ng-container *ngIf="{{column}}" matColumnDef="{{column}}" *ngFor="let column of displayedColumns">
<th mat-header-cell *matHeaderCellDef> {{column}} </th>
<td mat-cell *matCellDef="let element"
[ngClass]="{missing: element.status=='Missing' }"> {{element[column]}} </td>
</ng-container>
...
and don't forget to create the css for the .missing class we are referencing in the ngClass directive:
.missing {
background-color: #ec9d9d;
color: red
}

Add a html button to each row dynamically in angular material table

I have done a code to generate a datatable based on dynamic rows and columns. I need to add a html button in the last column of each row for edit. How to do that. Below is the code
<div class="example-table-container" >
<table mat-table [dataSource]="data" class="example-table"
matSort matSortActive="created" matSortDisableClear matSortDirection="desc">
<ng-container *ngFor="let disCol of this.columnHeading | keyvalue; let colIndex = index" matColumnDef="{{disCol.key}}">
<th mat-header-cell *matHeaderCellDef><b>{{disCol.value}}</b></th>
<td mat-cell *matCellDef="let element">{{element[disCol.key]}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
When i insert html code in value the button is not appearing but only html code is appearing like this
<button>Edit</button>
I have tried the bypassSecurityTrustHtml also. It gives the error safeValue must use [property]=binding:
Angular material table provides a stickyend property with some css.
Normally we add an extra th with stickyend to solve that.
Below is an example of that..
<ng-container matColumnDef="star" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element">
<mat-icon>more_vert</mat-icon>
</td>
</ng-container>
https://stackblitz.com/angular/nneoarrlekb?file=src%2Fapp%2Ftable-sticky-columns-example.html
<table mat-table [dataSource]="data" class="example-table"
matSort matSortActive="created" matSortDisableClear matSortDirection="desc">
<ng-container *ngFor="let disCol of this.columnHeading | keyvalue; let colIndex = index" matColumnDef="{{disCol.key}}">
<th mat-header-cell *matHeaderCellDef ><b >{{disCol.value}}</b></th>
<td mat-cell *matCellDef="let element" [innerHtml]="element[disCol.key]"></td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;">
</tr>
</table>
I have done this using the innerhtml
try to add this
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef></mat-header-cell>
<mat-cell *matCellDef="let row">
<button mat-icon-button (click)="onEdit(row)"><mat-icon>launch</mat-icon></button>
<button mat-icon-button color="warn" (click)="onDelete(row.$key)"><mat-icon>delete_outline</mat-icon></button>
</mat-cell>
</ng-container>
do not forget to add this column in displayedColumns
displayedColumns: string[] = ['fullName', 'email', 'mobile', 'city', 'departmentName', 'actions'];
I was in a similar predicament to you and this is what I came up with.
Firstly, trying to add the html button tag as a value is not a solution and akin to html injection.
Given an example set of columns,
const columnHeadings = ['position', 'firstName', 'lastName', 'actions'];
const displayNames = ['No.', 'First Name', 'Last Name', 'Actions'];
we may dynamically populate our table. What I have done here is presented different content for my one column, 'actions', based on a condition that is triggered by the desired column.
<table mat-table [dataSource]="data" class="example-table">
<ng-container matColumnDef="{{disCol}}" *ngFor="let disCol of columnHeadings; index as i">
<th mat-header-cell *matHeaderCellDef> {{displayNames[i]}} </th>
<td mat-cell *matCellDef="let element">
<div *ngIf="column!=='actions'"> {{element[disCol]}} </div>
<div *ngIf="column==='actions'">
<button mat-icon-button>
<mat-icon>edit</mat-icon>
</button>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnHeadings"></tr>
<tr mat-row *matRowDef="let element; columns: columnHeadings;" class="element-row"></tr>
</table>
By no means have a done a very elegant way to execute this but it is one that works; even if it is somewhat of a "hack" solution.

Is there any way to edit specific Column of table using mat-table in Angular material

In my project i am using Angular Material's table for displaying value in table format, As per new requirement i have to perform in-line editing for last 2 column over other along with that if user click 1st column the other column get highlighted automatically and everything has to be done using Angular material
This is the last 2 column i want to perform in-line editing
> <ng-container matColumnDef="weight">
> <th mat-header-cell *matHeaderCellDef> Weight </th>
> <td mat-cell *matCellDef="let element"> {{element.weight}} </td>
> </ng-container>
> <ng-container matColumnDef="symbol">
> <th mat-header-cell *matHeaderCellDef> Symbol </th>
> <td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
> </ng-container>
>
>
> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table>
How i achieved is as follows :
<tbody>
<tr *ngFor="let data of dataSource">
<td >{{data.position}}</td>
<td >{{data.name}}</td>
<td *ngIf="data.position === editRowId"> <input matInput [(ngModel)]="data.weight"></td>
<td *ngIf="data.position !== editRowId" (click)="editTableRow(data.position)">{{data.weight}}</td>
<td *ngIf="data.position === editRowId"> <input matInput [(ngModel)]="data.symbol"></td>
<td *ngIf="data.position !== editRowId" (click)="editTableRow(data.position)">{{data.symbol}}</td>
</tr>
</tbody>
TS file for above code :
export class AppComponent {
showEditTable = false;
editRowId: any = '';
selectedRow;
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
dataSource = ELEMENT_DATA;
editTableRow(val) {
console.log('click event started val = ' + val);
this.editRowId = val;
console.log('click event ended with val = ' + val);
}
}
I expect the result as table where last 2 column can be edited inline and at the same time i can send modified data to backend
Naman, it's the same, you need use <ng-container>to avoid create extra tags, so your columns becomes like
<!-- Weight Column -->
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef> Weight </th>
<td mat-cell *matCellDef="let element">
<ng-container *ngIf="element.position!==editRowId">
<span (click)="edit(element.position,'weigth')">{{element.weight}} </span>
</ng-container>
<ng-container *ngIf="element.position===editRowId">
<input matInput name="weigth" [(ngModel)]="element.weight">
</ng-container>
</td>
</ng-container>
Well, I call to function "edit" passign two arguments, the position and a string indicate the "name" of the input attributes. This allow us "focus" the input clicked. how?
We declare a ViewChildren of the MatInputs
#ViewChildren(MatInput,{read:ElementRef}) inputs:QueryList<ElementRef>;
See that we get not the MatInput else the "ElementRef". This allow us, in out function edit get the element with the attributes name equal the string that pass as argument, and focus it. See that we need "enclosed" all in a setTimeout to allow Angular to show the input
edit(row,element)
{
this.editRowId=row;
setTimeout(()=>{
this.inputs.find(x=>x.nativeElement.getAttribute('name')==element)
.nativeElement.focus()
})
}
You can see the full example in stackblitz
Well, in the example the data is hardcoded. Let's go to imagine that the data (and the structure) comes from a service data. The data is easy imagine because it's the same. The "structure" we can imagine as an array of object with three properties: name,head ad fixed. if fixed is true, we only show the data, else we can edit. So the only thing we need is create the columns in a *ngFor
First we are going to see how our schema can be defined. It's only an array
[
{name:'position',head:"No.",fixed:true},
{name:'name',head:"Name",fixed:true},
{name:'weight',head:"Weigth",fixed:false},
{name:'symbol',head:"Symbol",fixed:false},
]
Our table becomes like
<table #table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let column of schema;let last=last">
<ng-container [matColumnDef]="column.name">
<th mat-header-cell *matHeaderCellDef> {{column.head}} </th>
<td mat-cell *matCellDef="let element">
<ng-container *ngIf="element[schema[0].name]!==editRowId || column.fixed">
<span
(click)="column.fixed?editRowId=-1:
edit(element[schema[0].name],column.name)">
{{element[column.name]}}
</span>
</ng-container>
<ng-container *ngIf="element[schema[0].name]===editRowId && !column.fixed">
<input matInput [id]="column.name"
[(ngModel)]="element[column.name]"
(blur)="last?editRowId=-1:null">
</ng-container>
</td>
</ng-container>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
See that we "replace" element.position by element[column[0].name] -I supouse the first element of the schema will be the "key" and how we using [(ngModel)]="elemen[column.name]". Yes, to refererd to element.position we can refered too as elemen["position"] and remember we are iterating over "schema"
Another thing is that we are going to use "id", not "name". this is because if we using name, really Angular put as attrib some like: ng-reflect-name, and this don't allow us focus the input.
Finally we are get the data in the ngOnInit. We are going to use forkJoin to get together the schema and the data. A forkJoin only call and array of observables (in this case this.dataService.getSchema() and this.dataServide.getData, and return in an array the response of all the observables. We use the way
([variable1,variable2]) to store in "variable1" the first result and in variable2 the second result
ngOnInit()
{
forkJoin([this.dataService.getSchema(),this.dataService.getData()])
.subscribe(([schema,data])=>{
this.dataSource=data;
this.displayedColumns=schema.map(x=>x.name)
this.schema=schema
})
}
The displayedColumns must be an array with the names of the columns, but we need to store in an array the "schema" too.
In the stackblitz I create a service and "simulate" the observable using the rxjs creation operator of, in a real application the data becomes from a httpClient.get(....)

Angular material 2 table - define column using TemplateRef and ngTemplateOutlet

I am trying to make reusable material table and I want to use TemplateRef with ngTemplateOutlet to generate columns. In this example I created cards components which is using my material-table component. In cards.component.html I have template of one of my table's column. Running the project will cause an error ERROR TypeError: Cannot read property 'template' of undefined (see console on stackblitz). Is it possible to pass columnt template to my MaterialTableComponent and use it to define column?
<table mat-table [dataSource]="data" class="mat-elevation-z4">
<ng-container
*ngFor="let column of displayedColumnObjects"
[matColumnDef]="column.id"
>
<!-- if where is cellTemplate in table config json, use cellTemplate -->
<ng-container
*ngIf="column.cellTemplate"
[ngTemplateOutlet]="column.cellTemplate"
>
</ng-container>
<ng-container *ngIf="!column.cellTemplate">
<th mat-header-cell *matHeaderCellDef> {{column.title}} </th>
<td mat-cell *matCellDef="let element"> {{element[column.id]}} </td>
</ng-container>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let row; columns: columnsToDisplay;"></tr>
</table>
UPDATE
I found solution using ngTemplateOutletContext.
<table mat-table [dataSource]="data" class="mat-elevation-z4">
<ng-container
*ngFor="let column of displayedColumnObjects"
[matColumnDef]="column.id"
>
<ng-container
*ngIf="column.cellTemplate"
>
<th mat-header-cell *matHeaderCellDef> {{column.title}} </th>
<td mat-cell *matCellDef="let element">
<ng-template
[ngTemplateOutletContext]="{
element: element[column.id]
}"
[ngTemplateOutlet]="column.cellTemplate">
</ng-template>
</td>
</ng-container>
<ng-container *ngIf="!column.cellTemplate">
<th mat-header-cell *matHeaderCellDef> {{column.title}} </th>
<td mat-cell *matCellDef="let element"> {{element[column.id]}} </td>
</ng-container>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let row; columns: columnsToDisplay;"></tr>
</table>
See my example
ADD THE FOLLOWING IN THE COMPONENT TO BE RE-USED
In the '.HTML' file:
<ng-container [ngTemplateOutlet] = "yourNameReference"
[ngTemplateOutletContext] = "{$ implicit: {name: 'Arthur', lastName: 'Lemos'}">
</ng-container>
[NgTemplateOutletContext] data will be sent to the parent component. Remembering that '$ implicit' can receive any data you want to send to the PAI component
In the '.ts' file OF THE REUSABLE COMPONENT add the following code WITHIN YOUR CLASS
#ContentChild ('yourNameReference') yourNameReference: TemplateRef <any>;
FATHER COMPONENT (RECEIVES THE RE-USED COMPONENT)
Add the following code to the '.html' file
<ng-template #yourNameReference let-myDataa>
<p> {{myDataa.name}} {{myDataa.lastName}} </p>
</ng-template>
That way you can send data from the child component to the parent component, so you can create dynamic columns for your table.
Just follow the steps above, however, applying the specific columns of the table ... EX:
<td> <ng-container [ngTemplateOutlet] = "yourNameReference"
[ngTemplateOutletContext] = "{$ implicit: {name: 'Arthur', lastName: 'Lemos'}">
</ng-container> </td>
Add to the child's .ts:
#ContentChild ('yourNameReference') yourNameReference: TemplateRef <any>;
In the parent component:
<my-table>
...
<ng-template #yourNameReference let-myDataa>
<p> {{myDataa.name}} {{myDataa.lastName}} </p>
</ng-template>
<! - this content will be rendered inside the table ... ->
...
</my-table>
YOU CAN CREATE A REFERENCE NAME FOR EACH COLUMN, AND REPEAT THE PROCESS
ANGLE VERSION: 9
DOCUMENTATION: https://angular.io/api/common/NgTemplateOutlet
The documentation does not show the use of ngTemplateOutlet in different components, but it already helps in something.