DevExtreme v26.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

Angular Data Grid - Advanced Master-Detail View

This demo illustrates an advanced master-detail view in the DataGrid component. Master rows represent suppliers. Detail sections contain TabPanel components with two tabs: Orders and Address. In the Orders tab, you can choose a supplier's product from the SelectBox, and the DataGrid under it will show orders placed on this product. The Address tab displays the supplier's address.

Backend API
<dx-data-grid id="gridContainer" [dataSource]="suppliersData" [remoteOperations]="true" [showBorders]="true" > <dxo-data-grid-paging [pageSize]="15"></dxo-data-grid-paging> <dxo-data-grid-pager [visible]="true"></dxo-data-grid-pager> <dxi-data-grid-column dataField="ContactName"></dxi-data-grid-column> <dxi-data-grid-column dataField="ContactTitle"></dxi-data-grid-column> <dxi-data-grid-column dataField="CompanyName"></dxi-data-grid-column> <dxi-data-grid-column dataField="City"></dxi-data-grid-column> <dxi-data-grid-column dataField="Country"></dxi-data-grid-column> <dxo-data-grid-master-detail [enabled]="true" template="masterDetail" ></dxo-data-grid-master-detail> <div *dxTemplate="let masterDetailItem of 'masterDetail'"> <detail-view [key]="masterDetailItem.key" [rowData]="masterDetailItem.data" ></detail-view> </div> </dx-data-grid>
import { bootstrapApplication } from '@angular/platform-browser'; import { Component, enableProdMode, provideZoneChangeDetection } from '@angular/core'; import { DxDataGridModule, DxFormModule, DxSelectBoxModule, DxTabPanelModule, } from 'devextreme-angular'; import * as AspNetData from 'devextreme-aspnet-data-nojquery'; import { DataSource } from 'devextreme-angular/common/data'; import { DetailViewComponent } from './detail-view/detail-view.component'; if (!/localhost/.test(document.location.host)) { enableProdMode(); } @Component({ selector: 'demo-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], imports: [ DxDataGridModule, DxFormModule, DxSelectBoxModule, DxTabPanelModule, DetailViewComponent, ], }) export class AppComponent { url = 'https://js.devexpress.com/Demos/NetCore/api/DataGridAdvancedMasterDetailView'; suppliersData: DataSource; constructor() { this.suppliersData = new DataSource({ store: AspNetData.createStore({ key: 'SupplierID', loadUrl: `${this.url}/GetSuppliers`, }), }); } } bootstrapApplication(AppComponent, { providers: [ provideZoneChangeDetection({ eventCoalescing: true, runCoalescing: true }), ], });
::ng-deep #gridContainer { height: 620px; } ::ng-deep .dx-datagrid-rowsview .dx-master-detail-row:not(.dx-datagrid-edit-form) > .dx-datagrid-group-space, ::ng-deep .dx-datagrid-rowsview .dx-master-detail-row:not(.dx-datagrid-edit-form) .dx-master-detail-cell { background-color: transparent; }
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/26.1.5/css/dx.light.css" /> </head> <body class="dx-viewport"> <div class="demo-container"> <demo-app>Loading...</demo-app> </div> <script src="https://cdn.jsdelivr.net/npm/zone.js@0.16.2/bundles/zone.umd.js"></script> <script src="./bundle.js" type="module"></script> </body> </html>
::ng-deep .form-container { padding: 20px; } ::ng-deep .address-form label { font-weight: bold; }
<dx-tab-panel> <dxi-tab-panel-item title="Orders" template="ordersTab"></dxi-tab-panel-item> <div *dxTemplate="let ordersTabItem of 'ordersTab'"> <dx-form labelLocation="top" class="form-container"> <dxi-form-item template="productSelectBox"> <dxo-form-label text="Product"></dxo-form-label> </dxi-form-item> <div *dxTemplate="let selectBoxItem of 'productSelectBox'"> <dx-select-box [value]="productIdBySupplier" [dataSource]="productsData" [inputAttr]="{ 'aria-label': 'Product' }" [deferRendering]="false" valueExpr="ProductID" displayExpr="ProductName" (onValueChanged)="handleValueChange($event)" > </dx-select-box> </div> <dxi-form-item template="orderHistory"> <dxo-form-label text="Order History"></dxo-form-label> </dxi-form-item> <div *dxTemplate="let order of 'orderHistory'"> <dx-data-grid [dataSource]="orderHistoryData" [showBorders]="true"> <dxo-data-grid-paging [pageSize]="5"></dxo-data-grid-paging> <dxi-data-grid-column dataField="OrderID"></dxi-data-grid-column> <dxi-data-grid-column dataField="OrderDate" dataType="date" ></dxi-data-grid-column> <dxi-data-grid-column dataField="ShipCountry"></dxi-data-grid-column> <dxi-data-grid-column dataField="ShipCity"></dxi-data-grid-column> <dxi-data-grid-column dataField="UnitPrice" format="currency" ></dxi-data-grid-column> <dxi-data-grid-column dataField="Quantity"></dxi-data-grid-column> <dxi-data-grid-column dataField="Discount" format="percent" ></dxi-data-grid-column> <dxo-data-grid-summary> <dxi-data-grid-total-item column="UnitPrice" summaryType="sum"> <dxo-data-grid-value-format format="currency" [precision]="2" ></dxo-data-grid-value-format> </dxi-data-grid-total-item> <dxi-data-grid-total-item column="Quantity" summaryType="count" ></dxi-data-grid-total-item> </dxo-data-grid-summary> </dx-data-grid> </div> </dx-form> </div> <dxi-tab-panel-item title="Address" template="addressTab" ></dxi-tab-panel-item> <div *dxTemplate="let addressTabItem of 'addressTab'"> <dx-form [formData]="rowData" [colCount]="2" [customizeItem]="customizeItemTemplate" class="address-form form-container" > <dxi-form-item dataField="Address"></dxi-form-item> <dxi-form-item dataField="City"></dxi-form-item> <dxi-form-item dataField="Region"></dxi-form-item> <dxi-form-item dataField="PostalCode"></dxi-form-item> <dxi-form-item dataField="Country"></dxi-form-item> <dxi-form-item dataField="Phone"></dxi-form-item> <div *dxTemplate="let item of 'formItem'"> <span>{{ item.editorOptions.value }}</span> </div> </dx-form> </div> </dx-tab-panel>
import { Component, Input, AfterViewInit, ChangeDetectorRef } from '@angular/core'; import { DataSource } from 'devextreme-angular/common/data'; import * as AspNetData from 'devextreme-aspnet-data-nojquery'; import { DxFormModule, DxDataGridModule, DxSelectBoxModule, DxTabPanelModule } from 'devextreme-angular'; import { DxFormTypes } from 'devextreme-angular/ui/form'; import { DxSelectBoxTypes } from 'devextreme-angular/ui/select-box'; @Component({ selector: 'detail-view', templateUrl: './detail-view.component.html', styleUrls: ['./detail-view.component.css'], imports: [ DxDataGridModule, DxFormModule, DxSelectBoxModule, DxTabPanelModule, ], }) export class DetailViewComponent implements AfterViewInit { @Input() key: number; @Input() rowData: object; url: string; productIdBySupplier: number; productsData: DataSource; orderHistoryData: DataSource; constructor(private changeDetectorRef: ChangeDetectorRef) { this.url = 'https://js.devexpress.com/Demos/NetCore/api/DataGridAdvancedMasterDetailView'; } ngAfterViewInit() { this.productsData = new DataSource({ store: AspNetData.createStore({ key: 'ProductID', loadParams: { SupplierID: this.key }, loadUrl: `${this.url}/GetProductsBySupplier`, onLoaded: (items) => this.setDefaultProduct(items), }), }); this.changeDetectorRef.detectChanges(); } setDefaultProduct(items) { const firstItem = items[0]; if (firstItem && this.productIdBySupplier === undefined) { this.productIdBySupplier = firstItem.ProductID; } this.changeDetectorRef.detectChanges(); } handleValueChange(e: DxSelectBoxTypes.ValueChangedEvent) { this.productIdBySupplier = e.value; this.orderHistoryData = new DataSource({ store: AspNetData.createStore({ key: 'OrderID', loadParams: { ProductID: e.value }, loadUrl: `${this.url}/GetOrdersByProduct`, }), }); } customizeItemTemplate(item: DxFormTypes.SimpleItem) { item.template = 'formItem'; } }