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

# Master / Detail - Master Rows

Master Rows are the rows inside the Master Grid that can be expanded to display Detail Grids.

## Static Master Rows

Once a Master Grid is configured with `masterDetail=true`, all rows in the Master Grid behave as Master Rows, in that they can be expanded to display Detail Grids.

```ts
<ag-grid-angular
    [masterDetail]="masterDetail"
    /* other grid options ... */ />

// by itself, all rows will be expandable
this.masterDetail = true;
```

Because Static Master Rows are used in all the basic examples of Master / Detail, another example is not given here.

## Dynamic Master Rows

Dynamic Master Rows allows specifically deciding what rows in the Master Grid can be expanded. This can be useful if, for example, a Master Row has no child records, then it may not be desirable to allow expanding the Master Row.

To specify which rows should expand, provide the grid callback `isRowMaster`. The callback will be called once for each row. Return `true` to allow expanding and `false` to disallow expanding for that row.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `isRowMaster` | `IsRowMaster` |  |  | Callback to be used with [Master Detail](https://www.ag-grid.com/angular-data-grid/master-detail/) to determine if a row should be a master row. If `false` is returned no detail row will exist for this row. Module: [`MasterDetailModule`](https://www.ag-grid.com/angular-data-grid/modules/). |

```ts
<ag-grid-angular
    [masterDetail]="masterDetail"
    [isRowMaster]="isRowMaster"
    /* other grid options ... */ />

// turn on master detail
this.masterDetail = true;
// specify which rows to expand
this.isRowMaster = dataItem => {
    return expandThisRow ? true : false;
};
```

The following example only shows detail rows when there are corresponding child records.

#### Dynamic Master Rows

```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,
  IsRowMaster,
  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,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<ag-grid-angular
    style="width: 100%; height: 100%;"
    [masterDetail]="true"
    [isRowMaster]="isRowMaster"
    [columnDefs]="columnDefs"
    [defaultColDef]="defaultColDef"
    [detailCellRendererParams]="detailCellRendererParams"
    [rowData]="rowData"
    (firstDataRendered)="onFirstDataRendered($event)"
    (gridReady)="onGridReady($event)"
  /> `,
})
export class AppComponent {
  isRowMaster: IsRowMaster = (dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  };
  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,
  };
  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,
      },
    },
    getDetailRowData: function (params) {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>;
  rowData!: any[];

  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) {
    this.http
      .get<
        any[]
      >("https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json")
      .subscribe((data) => {
        this.rowData = data;
      });
  }
}
```

[Live example: Dynamic Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/dynamic/angular)

## Changing Dynamic Master Rows

The callback `isRowMaster` is re-called after data changes in the row as a result of a [Transaction Update](https://www.ag-grid.com/angular-data-grid/data-update-transactions/). This gives the opportunity to change whether the row is expandable or not.

```js
// to get isRowMaster called again, update the row using a Transaction Update
const transaction = { update: [ updatedRecord1, updatedRecord2 ] };
gridApi.applyTransaction(transaction);
```

In the example below, only Master Rows that have data to show are expandable. Note the following:

- Row 'Nora Thomas' has no detail records, thus is not expandable.
- Row 'Mila Smith' has detail records, thus is expandable.
- Clicking 'Clear Mila Calls' removes detail records from Mila Smith which results in the Mila Smith row no longer being a Master Row.
- Clicking 'Set Mila Calls' sets detail records from Mila Smith which results in the Mila Smith becoming a Master Row.

#### Dynamically Changing Master Rows

```ts
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  IsRowMaster,
  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([
  ClientSideRowModelApiModule,
  RowApiModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
]);
import { IAccount, ICallRecord } from "./interfaces";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div style="display: flex; flex-direction: column; height: 100%">
    <div style="padding-bottom: 4px">
      <button (click)="onBtClearMilaCalls()">Clear Mila Calls</button>
      <button (click)="onBtSetMilaCalls()">Set Mila Calls</button>
    </div>
    <ag-grid-angular
      style="width: 100%; height: 100%;"
      [masterDetail]="true"
      [isRowMaster]="isRowMaster"
      [columnDefs]="columnDefs"
      [defaultColDef]="defaultColDef"
      [getRowId]="getRowId"
      [detailCellRendererParams]="detailCellRendererParams"
      [rowData]="rowData"
      (firstDataRendered)="onFirstDataRendered($event)"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi<IAccount>;

  isRowMaster: IsRowMaster = (dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  };
  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,
  };
  getRowId: GetRowIdFunc = (params: GetRowIdParams) =>
    String(params.data.account);
  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,
      },
    },
    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);
  }

  onBtClearMilaCalls() {
    const milaSmithRowNode = this.gridApi.getRowNode("177001")!;
    const milaSmithData = milaSmithRowNode.data!;
    milaSmithData.callRecords = [];
    milaSmithData.calls = milaSmithData.callRecords.length;
    this.gridApi.applyTransaction({ update: [milaSmithData] });
  }

  onBtSetMilaCalls() {
    const milaSmithRowNode = this.gridApi.getRowNode("177001")!;
    const milaSmithData = milaSmithRowNode.data!;
    milaSmithData.callRecords = [
      {
        name: "susan",
        callId: 579,
        duration: 23,
        switchCode: "SW5",
        direction: "Out",
        number: "(02) 47485405",
      },
      {
        name: "susan",
        callId: 580,
        duration: 52,
        switchCode: "SW3",
        direction: "In",
        number: "(02) 32367069",
      },
    ];
    milaSmithData.calls = milaSmithData.callRecords.length;
    this.gridApi.applyTransaction({ update: [milaSmithData] });
  }

  onGridReady(params: GridReadyEvent<IAccount>) {
    this.gridApi = params.api;

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

[Live example: Dynamically Changing Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/changing-dynamic-1/angular)

The example below extends the previous example. It demonstrates a common scenario of the Master Row controlling the Detail Rows. Note the following:

- Each Master Row has buttons to add or remove one detail row.
- Clicking 'Add' will:
  - Add one detail row.
  - Ensure the Master Row is expandable.
  - Ensure the Master Row is expanded (i.e. the Detail Grid is visible).
- Clicking 'Remove' will:
  - Remove one detail row.
  - If no detail rows exist, ensure Master Row is not expandable

#### Dynamically Changing Master Rows

```ts
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import "./style.css";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  IsRowMaster,
  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,
  ClientSideRowModelApiModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
]);
import { CallsCellRenderer } from "./calls-cell-renderer.component";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular, CallsCellRenderer],
  template: `<ag-grid-angular
    style="width: 100%; height: 100%;"
    [masterDetail]="true"
    [isRowMaster]="isRowMaster"
    [columnDefs]="columnDefs"
    [defaultColDef]="defaultColDef"
    [getRowId]="getRowId"
    [detailCellRendererParams]="detailCellRendererParams"
    [rowData]="rowData"
    (firstDataRendered)="onFirstDataRendered($event)"
    (gridReady)="onGridReady($event)"
  /> `,
})
export class AppComponent {
  isRowMaster: IsRowMaster = (dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  };
  columnDefs: ColDef[] = [
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls", cellRenderer: CallsCellRenderer },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ];
  defaultColDef: ColDef = {
    flex: 1,
  };
  getRowId: GetRowIdFunc = (params: GetRowIdParams) =>
    String(params.data.account);
  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,
      },
    },
    getDetailRowData: (params) => {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>;
  rowData!: any[];

  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) {
    this.http
      .get<
        any[]
      >("https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json")
      .subscribe((data) => {
        this.rowData = data;
      });
  }
}
```

[Live example: Dynamically Changing Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/changing-dynamic-2/angular)

## Opening Master Rows by Default

Master Rows can be expanded by default using either `masterDefaultExpanded` or `isMasterOpenByDefault`.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `masterDefaultExpanded` | `number` |  |  | Master Detail: set to the number of levels of master rows to expand by default, e.g. `0` for none, `1` for first level only, etc. Set to `-1` to expand everything. If not set, falls back to `groupDefaultExpanded`. Module: [`MasterDetailModule`](https://www.ag-grid.com/angular-data-grid/modules/). |
| `isMasterOpenByDefault` | `IsMasterOpenByDefault` |  |  | (Client-side Row Model only) Master Detail: allows master rows to be open by default. Module: [`MasterDetailModule`](https://www.ag-grid.com/angular-data-grid/modules/). |

Set `masterDefaultExpanded` to the number of levels of Master Rows to expand by default, or `-1` to expand all Master Rows. If not set, it falls back to `groupDefaultExpanded`.

```js
const gridOptions = {
    // expand all master rows by default
    masterDefaultExpanded: -1,
};
```

For finer control, provide the `isMasterOpenByDefault` callback. It is called once for each Master Row; return `true` to expand that row by default.

```js
const gridOptions = {
    // expand specific master rows by default
    isMasterOpenByDefault: (params) => {
        return params.data.shouldExpand;
    },
};
```

> **Note**
>
> `isMasterOpenByDefault` applies to Master Rows, whereas `isGroupOpenByDefault` applies to group rows. When combining Master Detail with row grouping, each callback controls only its own row type.
