Credit:
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" cdkDropListGroup>
<ng-container *ngFor="let column of columns; let i = index" [matColumnDef]="column.field">
<th mat-header-cell *matHeaderCellDef>
{{ column.field }}
<span class="resize-handle" (mousedown)="onResizeColumn($event, i)"></span>
</th>
<td mat-cell *matCellDef="let row">
<!-- Formatting numbers using Pipes -->
<ng-container *ngIf="isNumber(row[column.field]); else textData">
{{ row[column.field] | number : '1.2-2' }}
</ng-container>
<ng-template #textData>
{{ row[column.field] }}
</ng-template>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
table {
width: 100%;
}
table th {
position: relative;
}
table th:hover .resize-handle {
opacity: 1;
transition: .3s ease-in-out;
}
.resize-handle {
display: inline-block;
border-right: 2px solid lightgray;
position: absolute;
top: 0;
right: 0;
height: 100%;
cursor: col-resize;
opacity: 0;
}
.resize-handle:hover {
width: 20px;
}
import { CommonModule } from '@angular/common';
import { AfterViewInit, Component, ElementRef, HostListener, Inject, OnInit, Renderer2, ViewChild } from '@angular/core';
import { MatTable, MatTableModule } from '@angular/material/table';
import { PLATFORM_ID } from "@angular/core";
import { isPlatformBrowser } from '@angular/common';
export interface PeriodicElement {
name: string;
position: number;
weight: number;
symbol: string;
}
const ELEMENT_DATA: PeriodicElement[] = [
{position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
{position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
{position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
{position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
{position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
{position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
{position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
];
@Component({
selector: 'app-resizable-table',
standalone: true,
imports: [MatTableModule, CommonModule],
templateUrl: './resizable-table.component.html',
styleUrl: './resizable-table.component.scss'
})
export class ResizableTableComponent implements OnInit, AfterViewInit {
title = 'Material Table column Resize';
@ViewChild(MatTable, {read: ElementRef} ) private matTableRef!: ElementRef;
columns: any[] = [
{ field: 'position', width: 20, },
{ field: 'name', width: 20, },
{ field: 'weight', width: 20, },
{ field: 'symbol', width: 20, }
];
displayedColumns: string[] = [];
dataSource = ELEMENT_DATA;
pressed = false;
currentResizeIndex!: number;
startX!: number;
startWidth!: number;
isResizingRight!: boolean;
resizableMousemove!: () => void;
resizableMouseup!: () => void;
constructor(
private renderer: Renderer2,
// validate platformID for SSR
@Inject(PLATFORM_ID) private platformId: any,
) { }
ngOnInit() {
this.setDisplayedColumns();
}
ngAfterViewInit() {
this.setTableResize(this.matTableRef.nativeElement.clientWidth);
}
isNumber(value: any): boolean {
return typeof value === 'number';
}
setTableResize(tableWidth: number) {
let totWidth = 0;
this.columns.forEach(( column) => {
totWidth += column.width;
});
const scale = (tableWidth - 5) / totWidth;
this.columns.forEach(( column) => {
column.width *= scale;
this.setColumnWidth(column);
});
}
setDisplayedColumns() {
this.columns.forEach(( column, index) => {
column.index = index;
this.displayedColumns[index] = column.field;
});
}
onResizeColumn(event: any, index: number) {
console.log(event.target.parentElement);
this.checkResizing(event, index);
this.currentResizeIndex = index;
this.pressed = true;
this.startX = event.pageX;
this.startWidth = event.target.parentElement.clientWidth;
event.preventDefault();
this.mouseMove(index);
}
private checkResizing(event: any, index: any) {
const cellData = this.getCellData(index);
if ( ( index === 0 ) || ( Math.abs(event.pageX - cellData.right) < cellData.width / 2 && index !== this.columns.length - 1 ) ) {
this.isResizingRight = true;
} else {
this.isResizingRight = false;
}
}
private getCellData(index: number) {
const headerRow = this.matTableRef.nativeElement.children[0].querySelector('tr');
const cell = headerRow.children[index];
return cell.getBoundingClientRect();
}
mouseMove(index: number) {
this.resizableMousemove = this.renderer.listen('document', 'mousemove', (event) => {
if (this.pressed && event.buttons ) {
const dx = (this.isResizingRight) ? (event.pageX - this.startX) : (-event.pageX + this.startX);
const width = this.startWidth + dx;
if ( this.currentResizeIndex === index && width > 50 ) {
this.setColumnWidthChanges(index, width);
}
}
});
this.resizableMouseup = this.renderer.listen('document', 'mouseup', (event) => {
if (this.pressed) {
this.pressed = false;
this.currentResizeIndex = -1;
this.resizableMousemove();
this.resizableMouseup();
}
});
}
setColumnWidthChanges(index: number, width: number) {
const orgWidth = this.columns[index].width;
const dx = width - orgWidth;
if ( dx !== 0 ) {
const j = ( this.isResizingRight ) ? index + 1 : index - 1;
const newWidth = this.columns[j].width - dx;
if ( newWidth > 50 ) {
this.columns[index].width = width;
this.setColumnWidth(this.columns[index]);
this.columns[j].width = newWidth;
this.setColumnWidth(this.columns[j]);
}
}
}
setColumnWidth(column: any) {
// makes it resolve "document not defined" error that occurs when rendering apps in SSR
if(!isPlatformBrowser(this.platformId)) return;
const columnEls = Array.from( document.getElementsByClassName('mat-column-' + column.field) );
columnEls.forEach(( el: any ) => {
el.style.width = column.width + 'px';
});
}
@HostListener('window:resize', ['$event'])
onResize(event: any) {
this.setTableResize(this.matTableRef.nativeElement.clientWidth);
}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [ResizableTableComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
}
<app-resizable-table/>
Expandable rows:
Angular (forked) - StackBlitz
https://stackblitz.com/edit/angular-35wzca?file=main.ts