---
title: "Master / Detail - Other"
enterprise: true
framework: angular
version: "36.1.0"
---

# Master / Detail - Other

Here we discuss areas of Master / Detail that don't quite fit within the other sections of the documentation.

## Syncing Detail Scrolling with Master

By default, the Detail Grid takes up the width of the Master Grid and does not move when the Master Grid's columns are horizontally scrolled. This is because the Detail Grid is not sitting with the other Master Grid's cells, rather it is in a separate container that overlays the Master Grid's cells and takes up the full width of the grid ignoring all the Master Grid's columns.

The underlying feature of the grid that allows the Detail Grid to span the width of the Master Grid is called [Full Width Row](https://www.ag-grid.com/angular-data-grid/full-width-rows/).

It is possible to have the Detail Grid sit within the same container as the Master Grid's cells and hence move with the Master Grid's horizontal scrolling. This is achieved by [Embedding the Full Width Row](https://www.ag-grid.com/angular-data-grid/full-width-rows/#embedded-full-width-rows) and is set via the grid property `embedFullWidthRows=true` for the Master Grid. This tells the grid to layout (embed) the Detail Panel with the other rows.

In the example below, notice that horizontal scrolling is combined for both Master Grid and Detail Grid using the Embed Full Width Rows feature.

#### Detail scrolls with Master

```ts
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowApiModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
]);
import { IAccount } from "./interfaces";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<ag-grid-angular
    style="width: 100%; height: 100%;"
    [columnDefs]="columnDefs"
    [defaultColDef]="defaultColDef"
    [masterDetail]="true"
    [embedFullWidthRows]="true"
    [detailCellRendererParams]="detailCellRendererParams"
    [rowData]="rowData"
    (firstDataRendered)="onFirstDataRendered($event)"
    (gridReady)="onGridReady($event)"
  /> `,
})
export class AppComponent {
  columnDefs: ColDef[] = [
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls" },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ];
  defaultColDef: ColDef = {
    width: 300,
  };
  detailCellRendererParams: any = {
    detailGridOptions: {
      columnDefs: [
        { field: "callId" },
        { field: "direction" },
        { field: "number" },
        { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
        { field: "switchCode" },
      ],
    },
    getDetailRowData: (params) => {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>;
  rowData!: IAccount[];

  constructor(private http: HttpClient) {}

  onFirstDataRendered(params: FirstDataRenderedEvent) {
    // arbitrarily expand a row for presentational purposes
    setTimeout(() => {
      params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
    }, 0);
  }

  onGridReady(params: GridReadyEvent<IAccount>) {
    this.http
      .get<
        IAccount[]
      >("https://www.ag-grid.com/example-assets/master-detail-data.json")
      .subscribe((data) => {
        this.rowData = data;
      });
  }
}
```

[Live example: Detail scrolls with Master](https://www.ag-grid.com/examples/master-detail-other/detail-scrolls-with-master/angular)

If you are mixing Embed Full Width Rows and [Custom Detail](https://www.ag-grid.com/angular-data-grid/master-detail-custom-detail/), then be aware the Detail Panel will get rendered three times as follows:

- Pinned Left Columns
- Pinned Right Columns
- Pinned Centre Columns

This is because the Columns, and thus the Detail Panel, are appearing in three separate scrollable sections. The example below demonstrates this. Note the custom Detail Panel appears in all three sections.

#### Embed Custom Detail

```ts
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import "./style.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import { MasterDetailModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowApiModule,
  ClientSideRowModelModule,
  MasterDetailModule,
]);
import { DetailCellRenderer } from "./detail-cell-renderer.component";
import { IAccount } from "./interfaces";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular, DetailCellRenderer],
  template: `<ag-grid-angular
    style="width: 100%; height: 100%;"
    [masterDetail]="true"
    [detailCellRenderer]="detailCellRenderer"
    [detailRowHeight]="detailRowHeight"
    [columnDefs]="columnDefs"
    [defaultColDef]="defaultColDef"
    [embedFullWidthRows]="true"
    [rowData]="rowData"
    (firstDataRendered)="onFirstDataRendered($event)"
    (gridReady)="onGridReady($event)"
  /> `,
})
export class AppComponent {
  detailCellRenderer: any = DetailCellRenderer;
  detailRowHeight = 150;
  columnDefs: ColDef[] = [
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer", pinned: "left" },
    { field: "account" },
    { field: "calls" },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
    { headerName: "Extra Col 1", valueGetter: '"AAA"' },
    { headerName: "Extra Col 2", valueGetter: '"BBB"' },
    { headerName: "Extra Col 3", valueGetter: '"CCC"' },
    { headerName: "Pinned Right", pinned: "right" },
  ];
  defaultColDef: ColDef = {};
  rowData!: IAccount[];

  constructor(private http: HttpClient) {}

  onFirstDataRendered(params: FirstDataRenderedEvent) {
    setTimeout(() => {
      params.api.forEachNode(function (node) {
        node.setExpanded(node.id === "1");
      });
    }, 1000);
  }

  onGridReady(params: GridReadyEvent<IAccount>) {
    this.http
      .get<
        IAccount[]
      >("https://www.ag-grid.com/example-assets/master-detail-data.json")
      .subscribe((data) => {
        this.rowData = data;
      });
  }
}
```

[Live example: Embed Custom Detail](https://www.ag-grid.com/examples/master-detail-other/embed-custom-detail/angular)

If you want your custom Detail Panel to only show content in one section, have logic that inspects the components `params.pinned` property and renders content relevant for the section.

## Filtering and Sorting

There are no specific configurations for filtering and sorting with Master / Detail but as there are multiple grids each grid will filter and sort independently.

Below shows a simple Master / Detail setup which has filtering and sorting enabled in both master and detail grids.

#### Filtering with Sort

```ts
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  MasterDetailModule,
  SetFilterModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowApiModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
]);
import { IAccount } from "./interfaces";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<ag-grid-angular
    style="width: 100%; height: 100%;"
    [columnDefs]="columnDefs"
    [defaultColDef]="defaultColDef"
    [masterDetail]="true"
    [detailCellRendererParams]="detailCellRendererParams"
    [rowData]="rowData"
    (firstDataRendered)="onFirstDataRendered($event)"
    (gridReady)="onGridReady($event)"
  /> `,
})
export class AppComponent {
  columnDefs: ColDef[] = [
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls" },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ];
  defaultColDef: ColDef = {
    flex: 1,
    filter: true,
  };
  detailCellRendererParams: any = {
    detailGridOptions: {
      columnDefs: [
        { field: "callId" },
        { field: "direction" },
        { field: "number", minWidth: 150 },
        { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
        { field: "switchCode", minWidth: 150 },
      ],
      defaultColDef: {
        flex: 1,
        filter: true,
      },
    },
    getDetailRowData: (params) => {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>;
  rowData!: IAccount[];

  constructor(private http: HttpClient) {}

  onFirstDataRendered(params: FirstDataRenderedEvent) {
    // arbitrarily expand a row for presentational purposes
    setTimeout(() => {
      params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
    }, 0);
  }

  onGridReady(params: GridReadyEvent<IAccount>) {
    this.http
      .get<
        IAccount[]
      >("https://www.ag-grid.com/example-assets/master-detail-data.json")
      .subscribe((data) => {
        this.rowData = data;
      });
  }
}
```

[Live example: Filtering with Sort](https://www.ag-grid.com/examples/master-detail-other/filtering-with-sort/angular)

## Grouping

The Master / Detail is supported with [Row Grouping](https://www.ag-grid.com/angular-data-grid/grouping/). Only leaf nodes can be master rows.

#### Grouping with Master Detail

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  IRowNode,
  IsRowMaster,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  MasterDetailModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import { IAccount, ICallRecord, accountsData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowApiModule,
  ClientSideRowModelModule,
  RowGroupingModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<ag-grid-angular
    style="width: 100%; height: 100%;"
    [columnDefs]="columnDefs"
    [rowData]="rowData"
    [defaultColDef]="defaultColDef"
    [autoGroupColumnDef]="autoGroupColumnDef"
    [masterDetail]="true"
    [getRowId]="getRowId"
    [isRowMaster]="isRowMaster"
    [detailCellRendererParams]="detailCellRendererParams"
    (firstDataRendered)="onFirstDataRendered($event)"
  /> `,
})
export class AppComponent {
  columnDefs: ColDef[] = [
    { field: "region", rowGroup: true, hide: true },
    { field: "name" },
    { field: "account" },
  ];
  rowData: IAccount[] | null = accountsData;
  defaultColDef: ColDef = {
    flex: 1,
    filter: true,
  };
  autoGroupColumnDef: AutoGroupColumnDef = {
    field: "region",
    headerName: "Region",
  };
  getRowId: GetRowIdFunc = (params) => params.data.id;
  isRowMaster: IsRowMaster = (dataItem: IAccount) =>
    dataItem.callRecords.length > 0;
  detailCellRendererParams: any = {
    detailGridOptions: {
      columnDefs: [
        { field: "callId" },
        { field: "number", minWidth: 150 },
        { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
      ],
      defaultColDef: {
        flex: 1,
        filter: true,
      },
    },
    getDetailRowData: (params) => {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>;

  onFirstDataRendered(params: FirstDataRenderedEvent) {
    // arbitrarily expand a row for presentational purposes
    setTimeout(() => {
      expandAllParents(params.api.getRowNode("1"));
    }, 0);
  }
}

function expandAllParents(row: IRowNode | null | undefined) {
  let current = row;
  while (current && current.level >= 0) {
    current.setExpanded(true);
    current = current.parent;
  }
}
```

[Live example: Grouping with Master Detail](https://www.ag-grid.com/examples/master-detail-other/grouping-master-detail/angular)

## Tree Data

The Master / Detail is supported with [Tree Data](https://www.ag-grid.com/angular-data-grid/tree-data/). Both leaf nodes and groups can be master rows. If a group is also a master row, the detail grid will be shown before its children.

> **Note**
>
> Master / Detail will by default show detail grids for every row, including groups. To show detail grids for leaf nodes only, provide the `isRowMaster` callback and return `true` for leaf nodes only.
>
> When using [Tree Data Paths](https://www.ag-grid.com/angular-data-grid/tree-data-paths/), filler groups cannot be master rows and `isRowMaster` will not be called.

#### Tree Data with Master Detail

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  IsRowMaster,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  MasterDetailModule,
  SetFilterModule,
  TreeDataModule,
} from "ag-grid-enterprise";
import { Fact, VegetableNode, vegetablesData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowApiModule,
  ClientSideRowModelModule,
  TreeDataModule,
  MasterDetailModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<ag-grid-angular
    style="width: 100%; height: 100%;"
    [columnDefs]="columnDefs"
    [rowData]="rowData"
    [treeData]="true"
    [treeDataChildrenField]="treeDataChildrenField"
    [autoGroupColumnDef]="autoGroupColumnDef"
    [masterDetail]="true"
    [detailCellRendererParams]="detailCellRendererParams"
    [isRowMaster]="isRowMaster"
    [groupDefaultExpanded]="groupDefaultExpanded"
  /> `,
})
export class AppComponent {
  columnDefs: ColDef[] = [{ field: "origin" }];
  rowData: VegetableNode[] | null = vegetablesData;
  treeDataChildrenField = "children";
  autoGroupColumnDef: AutoGroupColumnDef = {
    headerName: "Category",
    field: "name",
    flex: 1,
    cellRendererParams: {
      suppressCount: true,
    },
  };
  detailCellRendererParams: any = {
    detailGridOptions: {
      columnDefs: [{ field: "description", flex: 1 }, { field: "importance" }],
    },
    getDetailRowData: (params) => {
      params.successCallback(params.data.facts ?? []);
    },
  } as IDetailCellRendererParams<VegetableNode, Fact>;
  isRowMaster: IsRowMaster = (dataItem: VegetableNode) =>
    !!dataItem.facts?.length;
  groupDefaultExpanded = 1;
}
```

[Live example: Tree Data with Master Detail](https://www.ag-grid.com/examples/master-detail-other/tree-data-master-detail/angular)

## Layouts

It is not possible to mix [DOM layout](https://www.ag-grid.com/angular-data-grid/grid-size/#dom-layout) for master detail. This is because the layout is a CSS setting that would be inherited by all grids contained with the master grid. So if your master grid was 'for-print', then all child grids would pick up the 'for-print' layout.

When using Master / Detail and [for-print](https://www.ag-grid.com/angular-data-grid/printing/), then all detail grids need to use for-print.

When using Master / Detail and [auto-height](https://www.ag-grid.com/angular-data-grid/grid-size/#auto-height-layout), then all detail grids need to use auto-height.

## Cell Selection

When [Cell Selection](https://www.ag-grid.com/angular-data-grid/cell-selection/) is enabled on the Master Grid, the Detail Grid will not participate in the Cell Selection of the Master Grid.
