DevExtreme v24.2 is now available.

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

Your search did not match any results.

Angular Data Grid - Master-Detail View

The Master-Detail View allows users to expand a row ('master') and display the related content ('details') in the expanded section. To configure this feature, set the the masterDetail object's enabled property to true and use a template to specify detail section content. In this demo, the detail section displays another DataGrid.

Backend API
<dx-data-grid id="gridContainer" [dataSource]="employees" keyExpr="ID" [showBorders]="true" > <dxi-column dataField="Prefix" [width]="70" caption="Title"></dxi-column> <dxi-column dataField="FirstName"></dxi-column> <dxi-column dataField="LastName"></dxi-column> <dxi-column dataField="Position" [width]="170"></dxi-column> <dxi-column dataField="State" [width]="125"></dxi-column> <dxi-column dataField="BirthDate" dataType="date"></dxi-column> <dxo-master-detail [enabled]="true" template="detail"></dxo-master-detail> <div *dxTemplate="let employee of 'detail'"> <div class="master-detail-caption">{{ employee.data.FirstName + " " + employee.data.LastName + "'s Tasks:" }}</div> <detail-grid [key]="employee.key"></detail-grid> </div> </dx-data-grid>
import { NgModule, Component, enableProdMode } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { DxDataGridModule, DxTemplateModule } from 'devextreme-angular'; import { Employee, Service } from './app.service'; import { DetailGridComponent } from './detail-grid/detail-grid.component'; if (!/localhost/.test(document.location.host)) { enableProdMode(); } @Component({ selector: 'demo-app', templateUrl: `app/app.component.html`, styleUrls: [`app/app.component.css`], providers: [Service], }) export class AppComponent { employees: Employee[]; constructor(private service: Service) { this.employees = service.getEmployees(); } } @NgModule({ imports: [ BrowserModule, DxDataGridModule, DxTemplateModule, ], declarations: [AppComponent, DetailGridComponent], bootstrap: [AppComponent], }) export class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule);
::ng-deep #gridContainer { height: 440px; } ::ng-deep .master-detail-caption { padding: 0 0 5px 10px; font-size: 14px; font-weight: bold; }
import { Injectable } from '@angular/core'; export class Task { ID: number; Subject: string; StartDate: string; DueDate: string; Status: string; Priority: string; Completion: number; EmployeeID: number; } export class Employee { ID: number; FirstName: string; LastName: string; Prefix: string; Position: string; BirthDate: string; State: string; } const employees: Employee[] = [{ ID: 1, Prefix: 'Mr.', FirstName: 'John', LastName: 'Heart', Position: 'CEO', State: 'California', BirthDate: '1964/03/16', }, { ID: 2, Prefix: 'Mrs.', FirstName: 'Olivia', LastName: 'Peyton', Position: 'Sales Assistant', State: 'California', BirthDate: '1981/06/03', }, { ID: 3, Prefix: 'Mr.', FirstName: 'Robert', LastName: 'Reagan', Position: 'CMO', State: 'Arkansas', BirthDate: '1974/09/07', }, { ID: 4, Prefix: 'Ms.', FirstName: 'Greta', LastName: 'Sims', Position: 'HR Manager', State: 'Georgia', BirthDate: '1977/11/22', }, { ID: 5, Prefix: 'Mr.', FirstName: 'Brett', LastName: 'Wade', Position: 'IT Manager', State: 'Idaho', BirthDate: '1968/12/01', }, { ID: 6, Prefix: 'Mrs.', FirstName: 'Sandra', LastName: 'Johnson', Position: 'Controller', State: 'Utah', BirthDate: '1974/11/15', }, { ID: 7, Prefix: 'Mr.', FirstName: 'Kevin', LastName: 'Carter', Position: 'Shipping Manager', State: 'California', BirthDate: '1978/01/09', }, { ID: 8, Prefix: 'Ms.', FirstName: 'Cynthia', LastName: 'Stanwick', Position: 'HR Assistant', State: 'Arkansas', BirthDate: '1985/06/05', }, { ID: 9, Prefix: 'Dr.', FirstName: 'Kent', LastName: 'Samuelson', Position: 'Ombudsman', State: 'Missouri', BirthDate: '1972/09/11', }]; const tasks: Task[] = [{ ID: 1, Subject: 'Prepare 2013 Financial', StartDate: '2013/01/15', DueDate: '2013/01/31', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 8, }, { ID: 2, Subject: 'Prepare 3013 Marketing Plan', StartDate: '2013/01/01', DueDate: '2013/01/31', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 5, }, { ID: 3, Subject: 'Update Personnel Files', StartDate: '2013/02/03', DueDate: '2013/02/28', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 2, }, { ID: 4, Subject: 'Review Health Insurance Options Under the Affordable Care Act', StartDate: '2013/02/12', DueDate: '2013/04/25', Status: 'In Progress', Priority: 'High', Completion: 50, EmployeeID: 2, }, { ID: 5, Subject: 'Choose between PPO and HMO Health Plan', StartDate: '2013/02/15', DueDate: '2013/04/15', Status: 'In Progress', Priority: 'High', Completion: 75, EmployeeID: 1, }, { ID: 6, Subject: 'Google AdWords Strategy', StartDate: '2013/02/16', DueDate: '2013/02/28', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 1, }, { ID: 7, Subject: 'New Brochures', StartDate: '2013/02/17', DueDate: '2013/02/24', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 1, }, { ID: 11, Subject: 'Rollout of New Website and Marketing Brochures', StartDate: '2013/02/20', DueDate: '2013/02/28', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 5, }, { ID: 12, Subject: 'Update Sales Strategy Documents', StartDate: '2013/02/20', DueDate: '2013/02/22', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 9, }, { ID: 15, Subject: 'Review 2012 Sales Report and Approve 2013 Plans', StartDate: '2013/02/23', DueDate: '2013/02/28', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 5, }, { ID: 16, Subject: 'Deliver R&D Plans for 2013', StartDate: '2013/03/01', DueDate: '2013/03/10', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 3, }, { ID: 20, Subject: 'Approve Hiring of John Jeffers', StartDate: '2013/03/02', DueDate: '2013/03/12', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 4, }, { ID: 20, Subject: 'Approve Hiring of John Jeffers', StartDate: '2013/03/02', DueDate: '2013/03/12', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 6, }, { ID: 21, Subject: 'Non-Compete Agreements', StartDate: '2013/03/12', DueDate: '2013/03/14', Status: 'Completed', Priority: 'Low', Completion: 100, EmployeeID: 2, }, { ID: 22, Subject: 'Update NDA Agreement', StartDate: '2013/03/14', DueDate: '2013/03/16', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 1, }, { ID: 23, Subject: 'Update Employee Files with New NDA', StartDate: '2013/03/16', DueDate: '2013/03/26', Status: 'Need Assistance', Priority: 'Normal', Completion: 90, EmployeeID: 4, }, { ID: 24, Subject: 'Update Employee Files with New NDA', StartDate: '2013/03/16', DueDate: '2013/03/26', Status: 'Need Assistance', Priority: 'Normal', Completion: 90, EmployeeID: 6, }, { ID: 25, Subject: 'Sign Updated NDA', StartDate: '2013/03/20', DueDate: '2013/03/25', Status: 'Completed', Priority: 'Urgent', Completion: 100, EmployeeID: 7, }, { ID: 26, Subject: 'Sign Updated NDA', StartDate: '2013/03/20', DueDate: '2013/03/25', Status: 'Completed', Priority: 'Urgent', Completion: 100, EmployeeID: 8, }, { ID: 27, Subject: 'Sign Updated NDA', StartDate: '2013/03/20', DueDate: '2013/03/25', Status: 'Need Assistance', Priority: 'Urgent', Completion: 25, EmployeeID: 9, }, { ID: 35, Subject: 'Update Revenue Projections', StartDate: '2013/03/24', DueDate: '2013/04/07', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 8, }, { ID: 36, Subject: 'Review Revenue Projections', StartDate: '2013/03/25', DueDate: '2013/04/06', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 9, }, { ID: 40, Subject: 'Provide New Health Insurance Docs', StartDate: '2013/03/28', DueDate: '2013/04/07', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 4, }, { ID: 41, Subject: 'Provide New Health Insurance Docs', StartDate: '2013/03/28', DueDate: '2013/04/07', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 6, }, { ID: 50, Subject: 'Give Final Approval for Refunds', StartDate: '2013/05/05', DueDate: '2013/05/15', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 2, }, { ID: 52, Subject: 'Review Product Recall Report by Engineering Team', StartDate: '2013/05/17', DueDate: '2013/05/20', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 1, }, { ID: 55, Subject: 'Review Overtime Report', StartDate: '2013/06/10', DueDate: '2013/06/14', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 7, }, { ID: 60, Subject: 'Refund Request Template', StartDate: '2013/06/17', DueDate: '2014/04/01', Status: 'Deferred', Priority: 'Normal', Completion: 0, EmployeeID: 9, }, { ID: 71, Subject: 'Upgrade Server Hardware', StartDate: '2013/07/22', DueDate: '2013/07/31', Status: 'Completed', Priority: 'Urgent', Completion: 100, EmployeeID: 7, }, { ID: 72, Subject: 'Upgrade Personal Computers', StartDate: '2013/07/24', DueDate: '2014/04/30', Status: 'In Progress', Priority: 'Normal', Completion: 85, EmployeeID: 7, }, { ID: 74, Subject: 'Decide on Mobile Devices to Use in the Field', StartDate: '2013/07/30', DueDate: '2013/08/02', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 3, }, { ID: 78, Subject: 'Try New Touch-Enabled WinForms Apps', StartDate: '2013/08/11', DueDate: '2013/08/15', Status: 'Completed', Priority: 'Normal', Completion: 100, EmployeeID: 3, }, { ID: 81, Subject: 'Review Site Up-Time Report', StartDate: '2013/08/24', DueDate: '2013/08/30', Status: 'Completed', Priority: 'Urgent', Completion: 100, EmployeeID: 5, }, { ID: 99, Subject: 'Submit D&B Number to ISP for Credit Approval', StartDate: '2013/11/04', DueDate: '2013/11/07', Status: 'Completed', Priority: 'High', Completion: 100, EmployeeID: 8, }, { ID: 117, Subject: 'Approval on Converting to New HDMI Specification', StartDate: '2014/01/11', DueDate: '2014/01/31', Status: 'Deferred', Priority: 'Normal', Completion: 75, EmployeeID: 3, }, { ID: 138, Subject: 'Review HR Budget Company Wide', StartDate: '2014/03/20', DueDate: '2014/03/25', Status: 'In Progress', Priority: 'Normal', Completion: 40, EmployeeID: 6, }, { ID: 145, Subject: 'Final Budget Review', StartDate: '2014/03/26', DueDate: '2014/03/27', Status: 'In Progress', Priority: 'High', Completion: 25, EmployeeID: 6, }]; @Injectable() export class Service { getEmployees() { return employees; } getTasks() { return tasks; } }
// In real applications, you should not transpile code in the browser. // You can see how to create your own application with Angular and DevExtreme here: // https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Create_a_DevExtreme_Application/ const componentNames = [ 'accordion', 'action-sheet', 'autocomplete', 'bar-gauge', 'box', 'bullet', 'button-group', 'button', 'calendar', 'chart', 'chat', 'check-box', 'circular-gauge', 'color-box', 'context-menu', 'data-grid', 'date-box', 'date-range-box', 'defer-rendering', 'diagram', 'draggable', 'drawer', 'drop-down-box', 'drop-down-button', 'file-manager', 'file-uploader', 'filter-builder', 'form', 'funnel', 'gallery', 'gantt', 'html-editor', 'linear-gauge', 'list', 'load-indicator', 'load-panel', 'lookup', 'map', 'menu', 'multi-view', 'nested', 'number-box', 'pagination', 'pie-chart', 'pivot-grid-field-chooser', 'pivot-grid', 'polar-chart', 'popover', 'popup', 'progress-bar', 'radio-group', 'range-selector', 'range-slider', 'recurrence-editor', 'resizable', 'responsive-box', 'sankey', 'scheduler', 'scroll-view', 'select-box', 'slider', 'sortable', 'sparkline', 'speed-dial-action', 'splitter', 'switch', 'tab-panel', 'tabs', 'tag-box', 'text-area', 'text-box', 'tile-view', 'toast', 'toolbar', 'tooltip', 'tree-list', 'tree-map', 'tree-view', 'validation-group', 'validation-summary', 'validator', 'vector-map', ]; window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, }, meta: { 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, '@angular/platform-browser-dynamic': { 'esModule': true, }, '@angular/platform-browser': { 'esModule': true, }, '@angular/core': { 'esModule': true, }, '@angular/common': { 'esModule': true, }, '@angular/common/http': { 'esModule': true, }, '@angular/animations': { 'esModule': true, }, '@angular/forms': { 'esModule': true, }, 'openai': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, map: { 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', /* @angular */ '@angular/compiler': 'bundles:@angular/compiler.umd.js', '@angular/platform-browser-dynamic': 'bundles:@angular/platform-browser-dynamic.umd.js', '@angular/core': 'bundles:@angular/core.umd.js', '@angular/core/primitives/signals': 'bundles:@angular/core.primitives.signals.umd.js', '@angular/common': 'bundles:@angular/common.umd.js', '@angular/common/http': 'bundles:@angular/common-http.umd.js', '@angular/platform-browser': 'bundles:@angular/platform-browser.umd.js', '@angular/platform-browser/animations': 'bundles:@angular/platform-browser.umd.js', '@angular/forms': 'bundles:@angular/forms.umd.js', /* devextreme */ 'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.12', 'devextreme/bundles/dx.all': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/bundles/dx.all.js', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.5', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.54', /* devextreme-angular umd maps */ 'devextreme-angular': 'bundles:devextreme-angular/devextreme-angular.umd.js', 'devextreme-angular/core': 'bundles:devextreme-angular/devextreme-angular-core.umd.js', 'devextreme-angular/http': 'bundles:devextreme-angular/devextreme-angular-http.umd.js', ...componentNames.reduce((acc, name) => { acc[`devextreme-angular/ui/${name}`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}.umd.js`; acc[`devextreme-angular/ui/${name}/nested`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}-nested.umd.js`; return acc; }, {}), 'tslib': 'npm:tslib/tslib.js', 'rxjs': 'npm:rxjs@7.5.3/dist/bundles/rxjs.umd.js', 'rxjs/operators': 'npm:rxjs@7.5.3/dist/cjs/operators/index.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'app': { main: './app.component.ts', defaultExtension: 'ts', }, 'devextreme': { defaultExtension: 'js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, 'rxjs': { defaultExtension: 'js', }, 'rxjs/operators': { defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.12/inferno/package.json', 'npm:rxjs@7.5.3/package.json', 'npm:rxjs@7.5.3/operators/package.json', 'npm:devexpress-diagram@2.2.5/package.json', 'npm:devexpress-gantt@4.1.54/package.json', ], }; System.config(window.config); // System.import('@angular/compiler').catch(console.error.bind(console));
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" 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/24.2.3/css/dx.light.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/zone.js@0.14.10/bundles/zone.umd.js"></script> <script src="https://unpkg.com/reflect-metadata@0.1.13/Reflect.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script src="config.js"></script> <script> System.import("app").catch(console.error.bind(console)); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <demo-app>Loading...</demo-app> </div> </body> </html>
<dx-data-grid [dataSource]="tasksDataSource" [showBorders]="true" [columnAutoWidth]="true" > <dxi-column dataField="Subject" dataType="string"></dxi-column> <dxi-column dataField="StartDate" dataType="date"></dxi-column> <dxi-column dataField="DueDate" dataType="date"></dxi-column> <dxi-column dataField="Priority" dataType="string"></dxi-column> <dxi-column caption="Completed" dataType="boolean" [calculateCellValue]="completedValue" ></dxi-column> </dx-data-grid>
import { Component, Input, AfterViewInit } from '@angular/core'; import DataSource from 'devextreme/data/data_source'; import ArrayStore from 'devextreme/data/array_store'; import { Service, Task } from '../app.service'; @Component({ selector: 'detail-grid', templateUrl: `app/detail-grid/detail-grid.component.html`, styleUrls: [`app/detail-grid/detail-grid.component.css`], providers: [Service], }) export class DetailGridComponent implements AfterViewInit { @Input() key: number; tasksDataSource: DataSource; tasks: Task[]; constructor(private service: Service) { this.tasks = service.getTasks(); } ngAfterViewInit() { this.tasksDataSource = new DataSource({ store: new ArrayStore({ data: this.tasks, key: 'ID', }), filter: ['EmployeeID', '=', this.key], }); } completedValue(rowData) { return rowData.Status == 'Completed'; } }