Row Details (Angular)

Sometimes rows are bound to data objects that contain more information than would fit easily on a regular grid.

In these scenarios, you may want to use the FlexGridDetailProvider class that is included with the wijmo.grid.detail module. The FlexGridDetailProvider will create a details section for each row that users can expand to see the rest of the data.

You can add anything you want to the detail row, including other grids. The sample below shows both how to display the data in another grid, as well as within standard HTML elements.

Learn about FlexGrid | FlexGrid API Reference

This example uses Angular.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; import '@angular/compiler'; import { Component, enableProdMode, ɵresolveComponentResources } from '@angular/core'; import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; import { ODataCollectionView } from '@mescius/wijmo.odata'; import { WjGridModule } from '@mescius/wijmo.angular2.grid'; import { WjGridDetailModule } from '@mescius/wijmo.angular2.grid.detail'; import { WjInputModule } from '@mescius/wijmo.angular2.input'; const url = 'https://services.odata.org/Northwind/Northwind.svc'; @Component({ standalone: true, imports: [WjGridModule, WjGridDetailModule, BrowserModule, WjInputModule], selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { categories: ODataCollectionView; products: ODataCollectionView; private _catToProductMap: Map<string, any> = new Map(); constructor() { this.categories = new ODataCollectionView(url, 'Categories', { fields: ['CategoryID', 'CategoryName', 'Description'] }); this.products = new ODataCollectionView(url, 'Products'); } getProducts(categoryID: string): any[] { let categoryProducts = this._catToProductMap.get(categoryID); if (!categoryProducts) { categoryProducts = this.products.items.filter(product => product.CategoryID === categoryID); this._catToProductMap.set(categoryID, categoryProducts); } return categoryProducts; } detailVisibilityMode = 'ExpandSingle'; maxHeight = 0; isAnimated = true; keyActionEnter = 'None'; readonly rowHasDetailFn = (row: any) => !(row.dataItem.CategoryID % 2); evenRowHasDetail = false; } enableProdMode(); // Resolve resources (templateUrl, styleUrls etc), After resolution all URLs have been converted into `template` strings. ɵresolveComponentResources(fetch).then(() => { // Bootstrap application bootstrapApplication(AppComponent).catch(err => console.error(err)); });
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo FlexGrid Row Details</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="container-fluid"> <h3>HTML in Row Details</h3> <p> This grid shows product categories on each row. Expanding the rows shows an HTML element with information about the products in that category: </p> <wj-flex-grid [itemsSource]="categories" [isReadOnly]="true"> <wj-flex-grid-column [header]="'Name'" [binding]="'CategoryName'"></wj-flex-grid-column> <wj-flex-grid-column [header]="'Description'" [binding]="'Description'" [width]="'*'"></wj-flex-grid-column> <ng-template wjFlexGridDetail [isAnimated]=true let-item="item"> ID: <b>{{item.CategoryID}}</b><br /> Name: <b>{{item.CategoryName}}</b><br /> Description: <b>{{item.Description}}</b><br /> Products: <b>{{getProducts(item.CategoryID).length}} items</b><br /> <ol> <li *ngFor="let p of getProducts(item.CategoryID)">{{p.ProductName}}</li> </ol> </ng-template> </wj-flex-grid> <h3>Grids in Row Details</h3> <p> You can add anything you want to the detail rows, including other grids. This example shows the same categories, but the detail row uses another grid to show the products: </p> <wj-flex-grid [autoGenerateColumns]=false [itemsSource]="categories" [isReadOnly]=true> <wj-flex-grid-column [header]="'Category Name'" [binding]="'CategoryName'" [width]="'*'"></wj-flex-grid-column> <wj-flex-grid-column [header]="'Description'" [binding]="'Description'" [width]="'2*'"></wj-flex-grid-column> <ng-template wjFlexGridDetail let-item="item" [detailVisibilityMode]="detailVisibilityMode" [maxHeight]="maxHeight" [isAnimated]="isAnimated" [keyActionEnter]="keyActionEnter" [rowHasDetail]="evenRowHasDetail ? rowHasDetailFn : null"> <wj-flex-grid [itemsSource]="getProducts(item.CategoryID)" [isReadOnly]="true" [headersVisibility]="'Column'"> <wj-flex-grid-column [header]="'ID'" [binding]="'ProductID'"></wj-flex-grid-column> <wj-flex-grid-column [header]="'Name'" [binding]="'ProductName'"></wj-flex-grid-column> <wj-flex-grid-column [header]="'Qty/Unit'" [binding]="'QuantityPerUnit'"></wj-flex-grid-column> <wj-flex-grid-column [header]="'Unit Price'" [binding]="'UnitPrice'"></wj-flex-grid-column> <wj-flex-grid-column [header]="'Discontinued'" [binding]="'Discontinued'"></wj-flex-grid-column> </wj-flex-grid> </ng-template> </wj-flex-grid> <div class="grid-options"> <wj-menu [(value)]="detailVisibilityMode" [header]="'detailVisibilityMode'"> <wj-menu-item [value]="'Code'">Code</wj-menu-item> <wj-menu-item [value]="'Selection'">Selection</wj-menu-item> <wj-menu-item [value]="'ExpandSingle'">ExpandSingle</wj-menu-item> <wj-menu-item [value]="'ExpandMulti'">ExpandMulti</wj-menu-item> </wj-menu> <wj-menu [(value)]="maxHeight" [header]="'maxHeight'"> <wj-menu-item [value]="0">null</wj-menu-item> <wj-menu-item [value]="100">100</wj-menu-item> <wj-menu-item [value]="200">200</wj-menu-item> <wj-menu-item [value]="300">300</wj-menu-item> <wj-menu-item [value]="400">400</wj-menu-item> <wj-menu-item [value]="500">500</wj-menu-item> </wj-menu> <wj-menu [(value)]="isAnimated" [header]="'isAnimated'"> <wj-menu-item [value]="true">True</wj-menu-item> <wj-menu-item [value]="false">False</wj-menu-item> </wj-menu> <wj-menu [(value)]="keyActionEnter" [header]="'keyActionEnter'"> <wj-menu-item [value]="'None'">None</wj-menu-item> <wj-menu-item [value]="'ToggleDetail'">ToggleDetail</wj-menu-item> </wj-menu> <wj-menu [(value)]="evenRowHasDetail" [header]="'rowDetails'"> <wj-menu-item [value]="false">All</wj-menu-item> <wj-menu-item [value]="true">Even rows only</wj-menu-item> </wj-menu> </div> </div>
.wj-flexgrid { max-height: 350px; } body { margin-bottom: 20pt; } .grid-options .wj-menu { margin: 0.25em 0.5em 0.25em 0; }
(function (global) { SystemJS.config({ transpiler: './plugin-typescript.js', typescriptOptions: { "target": "ES2022", "module": "system", "emitDecoratorMetadata": true, "experimentalDecorators": true, }, baseURL: 'node_modules/', meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'systemjs-plugin-css' } }, paths: { // paths serve as alias 'npm:': '' }, packageConfigPaths: [ '/node_modules/*/package.json', "/node_modules/@angular/*/package.json", "/node_modules/@mescius/*/package.json" ], map: { 'core-js': 'https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js', 'typescript': 'https://cdnjs.cloudflare.com/ajax/libs/typescript/5.2.2/typescript.min.js', "rxjs": "https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js", 'systemjs-plugin-css': 'https://cdn.jsdelivr.net/npm/systemjs-plugin-css@0.1.37/css.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'npm:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.angular2.chart.analytics": "npm:@mescius/wijmo.angular2.chart.analytics/index.js", "@mescius/wijmo.angular2.chart.animation": "npm:@mescius/wijmo.angular2.chart.animation/index.js", "@mescius/wijmo.angular2.chart.annotation": "npm:@mescius/wijmo.angular2.chart.annotation/index.js", "@mescius/wijmo.angular2.chart.finance.analytics": "npm:@mescius/wijmo.angular2.chart.finance.analytics/index.js", "@mescius/wijmo.angular2.chart.finance": "npm:@mescius/wijmo.angular2.chart.finance/index.js", "@mescius/wijmo.angular2.chart.hierarchical": "npm:@mescius/wijmo.angular2.chart.hierarchical/index.js", "@mescius/wijmo.angular2.chart.interaction": "npm:@mescius/wijmo.angular2.chart.interaction/index.js", "@mescius/wijmo.angular2.chart.radar": "npm:@mescius/wijmo.angular2.chart.radar/index.js", '@mescius/wijmo.angular2.chart.map': 'npm:@mescius/wijmo.angular2.chart.map/index.js', "@mescius/wijmo.angular2.chart": "npm:@mescius/wijmo.angular2.chart/index.js", "@mescius/wijmo.angular2.core": "npm:@mescius/wijmo.angular2.core/index.js", "@mescius/wijmo.angular2.gauge": "npm:@mescius/wijmo.angular2.gauge/index.js", "@mescius/wijmo.angular2.grid.detail": "npm:@mescius/wijmo.angular2.grid.detail/index.js", "@mescius/wijmo.angular2.grid.filter": "npm:@mescius/wijmo.angular2.grid.filter/index.js", "@mescius/wijmo.angular2.grid.grouppanel": "npm:@mescius/wijmo.angular2.grid.grouppanel/index.js", "@mescius/wijmo.angular2.grid.search": "npm:@mescius/wijmo.angular2.grid.search/index.js", "@mescius/wijmo.angular2.grid.multirow": "npm:@mescius/wijmo.angular2.grid.multirow/index.js", "@mescius/wijmo.angular2.grid.sheet": "npm:@mescius/wijmo.angular2.grid.sheet/index.js", '@mescius/wijmo.angular2.grid.transposed': 'npm:@mescius/wijmo.angular2.grid.transposed/index.js', '@mescius/wijmo.angular2.grid.transposedmultirow': 'npm:@mescius/wijmo.angular2.grid.transposedmultirow/index.js', "@mescius/wijmo.angular2.grid": "npm:@mescius/wijmo.angular2.grid/index.js", "@mescius/wijmo.angular2.input": "npm:@mescius/wijmo.angular2.input/index.js", "@mescius/wijmo.angular2.olap": "npm:@mescius/wijmo.angular2.olap/index.js", "@mescius/wijmo.angular2.viewer": "npm:@mescius/wijmo.angular2.viewer/index.js", "@mescius/wijmo.angular2.nav": "npm:@mescius/wijmo.angular2.nav/index.js", "@mescius/wijmo.angular2.directivebase": "npm:@mescius/wijmo.angular2.directivebase/index.js", '@mescius/wijmo.angular2.barcode.common': 'npm:@mescius/wijmo.angular2.barcode.common/index.js', '@mescius/wijmo.angular2.barcode.composite': 'npm:@mescius/wijmo.angular2.barcode.composite/index.js', '@mescius/wijmo.angular2.barcode.specialized': 'npm:@mescius/wijmo.angular2.barcode.specialized/index.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', "@angular/common/http": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/http.mjs", "@angular/core": "https://cdn.jsdelivr.net/npm/@angular/core@16.2.6/fesm2022/core.mjs", "@angular/platform-browser": "https://cdn.jsdelivr.net/npm/@angular/platform-browser@16.2.6/fesm2022/platform-browser.mjs", "@angular/common": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/common.mjs", "@angular/compiler": "https://cdn.jsdelivr.net/npm/@angular/compiler@16.2.6/fesm2022/compiler.mjs", "@angular/forms": "https://cdn.jsdelivr.net/npm/@angular/forms@16.2.6/fesm2022/forms.mjs", "@angular/localize": "https://cdn.jsdelivr.net/npm/@angular/localize@16.2.6/fesm2022/localize.mjs", "@angular/platform-browser-dynamic": "https://cdn.jsdelivr.net/npm/@angular/platform-browser-dynamic@16.2.6/fesm2022/platform-browser-dynamic.mjs", }, // packages tells the System loader how to load when no filename and/or no extension packages: { "./src": { defaultExtension: 'ts' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);