I am trying to add a show/hide button to show/hidden columns in an angular material table. I can't get the button to function properly. When clicking on the button it does show the columns in the table I want to show/hide. I'm also trying to get the button to be placed as the last column header of the table after the 'position' column header on the right or whatever column is the last column. Also want the column names for the button to show vertically instead of horizontally bunched together like they are now.
html
<button mat-button [matMenuTriggerFor]="menu" class="table-config-menu">
<mat-icon aria-hidden="false" aria-label="Example home icon">settings</mat-icon>
<mat-menu #menu="matMenu">
<span class="table-config-menu-label">Edit Columns</span>
<div class="table-config-menu-options">
<mat-checkbox
*ngFor="let cd of columnDefinitions; let i = index"
(click)="$event.stopPropagation()"
[(ngModel)]="cd.visible">
{{cd.label}}
</mat-checkbox>
</div>
</mat-menu>
</button>
<table
mat-table
[dataSource]="dataSource"
multiTemplateDataRows
class="mat-elevation-z8"
>
<ng-container
matColumnDef="{{column}}"
*ngFor="let column of columnsToDisplay"
>
<ng-container *ngIf="column !== 'action'; else action">
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
<td mat-cell *matCellDef="let element">{{element[column]}}</td>
</ng-container>
<ng-template #action>
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let element" class="checkbox-spacing">
<mat-icon
(click)="expandedElement = expandedElement === element ? null : element"
>{{expandedElement === element ? 'expand_less' :
'expand_more'}}</mat-icon
>
</td>
</ng-template>
</ng-container>
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox
(change)="$event ? toggleAllRows() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
[aria-label]="checkboxLabel()"
>
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let row">
<mat-checkbox
(click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)"
[aria-label]="checkboxLabel(row)"
>
</mat-checkbox>
</td>
</ng-container>
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
<ng-container matColumnDef="expandedDetail">
<td
mat-cell
*matCellDef="let element"
[attr.colspan]="columnsToDisplay.length"
>
<div
class="example-element-detail"
[@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'"
>
<div class="example-element-diagram">
<div class="example-element-position">{{element.position}}</div>
<div class="example-element-symbol">{{element.symbol}}</div>
<div class="example-element-name">{{element.name}}</div>
<div class="example-element-weight">{{element.weight}}</div>
</div>
<div class="example-element-description">
{{element.description}}
<span class="example-element-description-attribution">
-- Wikipedia
</span>
</div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
<tr
mat-row
*matRowDef="let element; columns: columnsToDisplayWithExpand;"
class="example-element-row"
[class.example-expanded-row]="expandedElement === element"
></tr>
<tr
mat-row
*matRowDef="let row; columns: ['expandedDetail']"
class="example-detail-row"
></tr>
<!-- <tr mat-header-row *matHeaderRowDef="getDisplayedColumns()">
</tr>
<tr mat-row *matRowDef="let row; columns: getDisplayedColumns()"></tr> -->
</table>
ts
import { SelectionModel } from '@angular/cdk/collections';
import { MatTableDataSource } from '@angular/material/table';
import { Component } from '@angular/core';
import { MatCheckboxModule } from '@angular/material/checkbox';
import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
/**
* @title Table with expandable rows
*/
@Component({
selector: 'table-expandable-rows-example',
styleUrls: ['table-expandable-rows-example.css'],
templateUrl: 'table-expandable-rows-example.html',
imports: [MatCheckboxModule],
animations: [
trigger('detailExpand', [
state('collapsed', style({ height: '0px', minHeight: '0' })),
state('expanded', style({ height: '*' })),
transition(
'expanded <=> collapsed',
animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')
),
]),
],
})
export class TableExpandableRowsExample {
columnsToDisplay: string[] = [
'action',
'Name',
'weight',
'symbol',
'position',
];
columnsToDisplayWithExpand = ['select', ...this.columnsToDisplay];
dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);
expandedElement: PeriodicElement | null;
selection = new SelectionModel<PeriodicElement>(true, []);
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
toggleAllRows() {
if (this.isAllSelected()) {
this.selection.clear();
return;
}
this.selection.select(...this.dataSource.data);
}
/** The label for the checkbox on the passed row */
checkboxLabel(row?: PeriodicElement): string {
if (!row) {
return `${this.isAllSelected() ? 'deselect' : 'select'} all`;
}
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${
row.position + 1
}`;
}
columnDefinitions = [
{ def: 'name', label: 'Name', visible: true },
{
def: 'weight',
label: 'Weight',
visible: true,
},
{
def: 'symbol',
label: 'Symbol',
visible: true,
},
{
def: 'position',
label: 'Position',
visible: true,
},
];
getDisplayedColumns(): string[] {
return this.columnDefinitions
.filter((cd) => cd.visible)
.map((cd) => cd.def);
}
}
export interface PeriodicElement {
Name: string;
position: number;
weight: number;
symbol: string;
description: string;
}
const ELEMENT_DATA: PeriodicElement[] = [
{
position: 1,
Name: 'Hydrogen',
weight: 1.0079,
symbol: 'H',
description: `Hydrogen is a chemical element with symbol H and atomic number 1. With a standard
atomic weight of 1.008, hydrogen is the lightest element on the periodic table.`,
},
{
position: 2,
Name: 'Helium',
weight: 4.0026,
symbol: 'He',
description: `Helium is a chemical element with symbol He and atomic number 2. It is a
colorless, odorless, tasteless, non-toxic, inert, monatomic gas, the first in the noble gas
group in the periodic table. Its boiling point is the lowest among all the elements.`,
},
{
position: 3,
Name: 'Lithium',
weight: 6.941,
symbol: 'Li',
description: `Lithium is a chemical element with symbol Li and atomic number 3. It is a soft,
silvery-white alkali metal. Under standard conditions, it is the lightest metal and the
lightest solid element.`,
},
];
/**
* Control column ordering and which columns are displayed.
*/
Demo code is on StackBlitz. I have commented out the getDisplayedColumns in the html so the rest of the table is still functioning properly. When I uncomment them the columns on the table disappear
To avoid unnecessary complexity, simply add a separate displayedColumns
array as main columns list, and when checkbox is changed, call method to mutate this array instead (add/remove item based on checked status, and to keep column order, use all columns array in combination with Set that tracks checked columns), and when used in <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
it will then handle itself when checkboxes/columns get toggled:
Also, to have menu as last column, add a toggle column definition, and add this to displayed columns as well.
And to display checkboxes vertically, just add some flex and padding:
ts:
toggleColDef = 'toggleCol';
displayedColumns: any = [
...this.columnsToDisplayWithExpand,
this.toggleColDef,
];
// all columns for tracking order
allCols = [...this.displayedColumns];
// use Set to track checked columns
checkedColumns = new Set(this.displayedColumns);
toggleColumn(col: string, checked: boolean) {
if (checked) {
// add to checked columns if checked
this.checkedColumns.add(col);
} else {
// remove
this.checkedColumns.delete(col);
}
// recreate columns in the same order, add toggle manually
this.displayedColumns = this.allCols.filter(el=>{
if(el === this.toggleColDef) {
return true;
} else {
return this.checkedColumns.has(el);
}
});
}
isColumnVisible(col: string): boolean {
return this.checkedColumns.has(col);
}
html
toggle cols def
<!-- toggle columns menu -->
<ng-container matColumnDef="{{toggleColDef}}">
<th mat-header-cell *matHeaderCellDef>
<button mat-button [matMenuTriggerFor]="menu" class="table-config-menu">
<mat-icon aria-hidden="false" aria-label="Example home icon"
>settings</mat-icon
>
<mat-menu #menu="matMenu" >
<span class="table-config-menu-label">Edit Columns</span>
<div class="table-config-menu-options" style="display:flex;flex-direction:column;padding:8px;">
<mat-checkbox
*ngFor="let cd of columnDefinitions; let i = index"
[checked]="isColumnVisible(cd.def)"
(change)="toggleColumn(cd.def, $event.checked)"
>
{{cd.label}}
</mat-checkbox>
</div>
</mat-menu>
</button>
</th>
<td mat-cell *matCellDef="let element">
</td>
</ng-container>
use displayedColumns
as main array
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let element; columns: displayedColumns;"
class="example-element-row"
[class.example-expanded-row]="expandedElement === element"
></tr>
<tr
mat-row
*matRowDef="let row; columns: ['expandedDetail']"
class="example-detail-row"
></tr>