I have a fairly standard setup of ag-grid with a nested component as follows :
import { Component } from '@angular/core';
import * as agGrid from 'ag-grid-community';
import { NestedMatExpansionPanelComponent } from './nested-mat-expansion-panel/nested-mat-expansion-panel.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'aggrid-material-expansion-panel';
dataToShow = [
{ name: 'name1', companyCountry: 'UK', reference: 'Ref 1' },
{ name: 'name2', companyCountry: 'Germany', reference: 'Ref 2' },
{ name: 'name3', companyCountry: 'France', reference: 'Ref 3' },
{ name: 'name4', companyCountry: 'UK', reference: 'Ref 4' },
{ name: 'name5', companyCountry: 'USA', reference: 'Ref 5' },
];
columnDefs = [
// tslint:disable-next-line: max-line-length
{ headerName: 'Name', colId: 'name', cellRenderer: 'nestedMatExpansionPanelRenderer', filter: false, sortable: false },
{ headerName: 'Country', field: 'companyCountry', sortable: true, filter: true },
{ headerName: 'Reference', field: 'reference', sortable: true, filter: true }
];
// agG_rid
gridApi: agGrid.GridApi;
gridColumnApi: agGrid.ColumnApi;
gridOptions: agGrid.GridOptions = {};
public defaultColDef: any;
public columnTypes: any;
public context: any;
public frameworkComponents: any;
public sortingOrder: any;
constructor() {
this.initTable();
}
public onGridReady(params: any) {
// this.gridApi = this.gridOptions.api;
// this.gridColumnApi = this.gridOptions.columnApi;
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
this.gridApi.sizeColumnsToFit();
}
public initTable(): void {
this.defaultColDef = {
flex: 1,
autoHeight: true,
editable: false,
enableBrowserTooltips: true,
resizable: true,
filter: 'agTextColumnFilter',
suppressMenu: true,
floatingFilterComponentParams: { suppressFilterButton: true },
filterParams: { newRowsAction: 'keep' },
sortable: true,
};
this.columnTypes = {
numericColumn: { filter: 'agnumericColumnFilter' },
dateColumn: {
filter: 'agDateColumnFilter',
filterParams: {
newRowsAction: 'keep',
comparator(filterLocalDateAtMidnight, cellValue) {
const dateParts = cellValue.split('/');
const day = Number(dateParts[2]);
const month = Number(dateParts[1]) - 1;
const year = Number(dateParts[0]);
const cellDate = new Date(day, month, year);
if (cellDate < filterLocalDateAtMidnight) {
return -1;
} else if (cellDate > filterLocalDateAtMidnight) {
return 1;
} else {
return 0;
}
}
}
}
};
this.sortingOrder = ['desc', 'asc'];
this.context = { componentParent: this };
this.frameworkComponents = {
nestedMatExpansionPanelRenderer: NestedMatExpansionPanelComponent,
};
}
public onRowClicked($event) {
//
}
}
As you can see I have
autoHeight: true,
so each row should automatically get the correct height depending on the expansion state of the embedded accordion but the output height of the row is calculated incorrectly:
How can I automatically adjust the height of each row depending on the state of the embedded expansion panels so there wont be any empty redundant space when I open or close the expansion panels inside each row?
Basically, the issues you are having is a combined issue of the MatExpansionPanel
and how autoHeight
is calculated in ag-grid.
With autoHeight they take the contents from the cell, and put this in a temporary invisible element probably at the document.body
element. The problem is that the proper styling is not applied to the element at this moment, and it shows the height you were facing. So, autoHeight
is not the solution here and should be set to false
.
So how to implement the manual height calculation. Ag-grid has a setRowHeight
property on the node
. Which you can use to set the height. From there the grid API needs to be informed that all rows have altered their heights and can recalculate on that basis, by calling onRowHeightChanged()
. One way to get this communication is to use a normal service:
@Injectable()
export class GridService {
readonly updateHeight$ = new Subject<void>();
}
From within the component where you create the ag-grid in the template, you should add this server to the providers array and listen to the subject (with a debounce to make sure all rows are calculated initially):
@Component({
// ...,
providers: [GridService]
})
export class GridComponent {
gridApi: agGrid.GridApi;
constructor(private gs: GridService) {
this.gs.updateHeight$.pipe(
debounceTime(1)
).subscribe(() => {
this.gridApi?.onRowHeightChanged();
});
}
onGridReady(params: any) {
this.gridApi = params.api;
}
}
Now we need the following logic in the cell renderer framework component:
export class ExpansionPanelComponent implements ICellRendererAngularComp, AfterViewInit {
public params: ICellRendererParams;
constructor(private el: ElementRef<HTMLElement>, private gs: GridService) {}
agInit(params: ICellRendererParams): void {
this.params = params;
}
ngAfterViewInit(): void {
// inside setTimeout because the accordion is not properly sized (bug) and it will be too big
setTimeout(() => this.updateHeight());
}
updateHeight(): void {
// inside setTimeout because the accordion is not properly sized (bug) and it will be too big
setTimeout(() => {
this.params?.node.setRowHeight(this.el.nativeElement.offsetHeight);
this.gs.updateHeight$.next();
});
}
refresh(params: any): boolean {
return false;
}
}
I've created a working pull request here
To remove the padding on the left, you can add the following to your columnDefs array:
columnDefs = [
{
//...,
cellRenderer: 'nestedMatExpansionPanelRenderer',
cellStyle: { 'padding-left': 0 }
},
// ...
]