{"id":3029,"date":"2025-08-25T08:06:36","date_gmt":"2025-08-25T08:06:36","guid":{"rendered":"https:\/\/www.infragistics.com\/blogs\/?p=3029"},"modified":"2025-10-03T07:16:52","modified_gmt":"2025-10-03T07:16:52","slug":"master-detail-layout","status":"publish","type":"post","link":"https:\/\/www.infragistics.com\/blogs\/master-detail-layout","title":{"rendered":"Getting Started with Master-Detail Layout Using Ignite UI for Angular Grid\u00a0"},"content":{"rendered":"\n<p>When building enterprise applications (such as CRMs, ERPs, and admin dashboards), it\u2019s common to work with relational datasets where one record is linked to multiple related records. For example, an order might have various items, a customer might have multiple transactions, or a department might have numerous employees.&nbsp;<\/p>\n\n\n\n<p>The <a href=\"https:\/\/www.infragistics.com\/products\/ignite-ui-angular\/angular\/components\/grid\/master-detail\">Master-Detail Layout<\/a> is a proven UI pattern for these scenarios. It provides a straightforward, scalable, and user-friendly way to present related data without overwhelming the screen.&nbsp;&nbsp;<\/p>\n\n\n\n<p>In this article, we\u2019ll walk you through building a clean, efficient master-detail interface using <a href=\"https:\/\/www.infragistics.com\/products\/ignite-ui-angular\">Ignite UI for Angular<\/a>. We&#8217;ll explore its powerful <a href=\"https:\/\/www.infragistics.com\/products\/ignite-ui-angular\/angular\/components\/grids-and-lists\">Grid component<\/a> and its built-in support for nested templates using igxGridDetail, helping you display hierarchical data with minimal setup and high performance.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"what-is-a-master-detail-layout\">What Is a Master-Detail Layout?&nbsp;<\/h2>\n\n\n\n<p>A Master-Detail Layout (also known as parent-child or expandable rows) is a design pattern where a top-level list (master) allows users to expand items to reveal related information (detail). Common examples include:&nbsp;<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Orders (master) with Order Items (detail)&nbsp;<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Users (master) with Profile Logs (detail)&nbsp;<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Departments (master) with Employees (detail)&nbsp;<\/li>\n<\/ul>\n\n\n\n<p>This pattern enhances usability by reducing screen clutter while providing access to deeper data when needed.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"setting-up-the-angular-project\">Setting Up the Angular Project&nbsp;<\/h2>\n\n\n\n<p>Before diving into the master-detail implementation, let\u2019s set up a clean Angular workspace for our demo.&nbsp;&nbsp;<\/p>\n\n\n\n<p><strong>1. Create a New Angular Project<\/strong>&nbsp;<\/p>\n\n\n\n<p>If you don\u2019t already have an Angular workspace ready, create one using the <a href=\"https:\/\/www.infragistics.com\/products\/ignite-ui-angular\/angular\/components\/general\/cli-overview\">Angular CLI<\/a>. Open this project in your preferred IDE or editor to begin.&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">ng new master-detail-grid <\/pre>\n\n\n\n<p><strong>2. Add Ignite UI for Angular<\/strong>&nbsp;<\/p>\n\n\n\n<p>Ignite UI for Angular offers powerful UI components, including the igxGrid we\u2019ll use for the master-detail layout. Here&#8217;s how to add it.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">ng add igniteui-angular<\/pre>\n\n\n\n<p><strong>3. Generate a Dedicated Demo Component<\/strong>&nbsp;<\/p>\n\n\n\n<p>To keep your master-detail example modular,&nbsp; generate a dedicated Angular component.&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">ng generate component pages\/grid-demo<\/pre>\n\n\n\n<p><strong>4. Configure Routing for the Demo<\/strong>&nbsp;<\/p>\n\n\n\n<p>To simplify navigation, replace your existing app.routes.ts (or your routing module) with a minimal setup that redirects to your demo component by default:&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import { Routes } from '@angular\/router'; \nexport const routes: Routes = [ \n  { path: '', redirectTo: '\/demo', pathMatch: 'full' }, \n  { path: 'demo', loadComponent: () => import('.\/pages\/grid-demo\/grid-demo.component').then(m => m.GridDemoComponent) }, \n  { path: '**', redirectTo: '\/demo' } \n]; <\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"setting-up-the-data\">Setting Up the Data&nbsp;<\/h2>\n\n\n\n<p>Before we build the master-detail layout, it\u2019s essential to prepare your data. This ensures your grid components have meaningful content to display and can efficiently fetch related detail data as users interact with the UI.&nbsp;<\/p>\n\n\n\n<p>You can use any data source with Ignite UI for Angular:&nbsp;<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Static\/local data &#8211; from a JSON file in your assets folder.&nbsp;<\/li>\n\n\n\n<li>Remote API data &#8211; fetched via Angular\u2019s HttpClient from a backend service.&nbsp;<\/li>\n\n\n\n<li>Mocked data &#8211; for development, generated in the service itself.&nbsp;<\/li>\n<\/ul>\n\n\n\n<p>In this example, we\u2019ll integrate with a custom Northwind Swagger API. The concepts remain the same regardless of your data source structure.&nbsp;<\/p>\n\n\n\n<p><strong>1. Define Your Models<\/strong>&nbsp;<\/p>\n\n\n\n<p>To enforce type safety and clarity, define TypeScript interfaces representing the data shapes in a dedicated models.ts file.&nbsp;&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">export interface Customer { \n  customerId: string; \n  companyName: string; \n  contactName: string; \n  country: string; \n} \nexport interface Order { \n  orderId: number; \n  customerId: string; \n  orderDate: string; \n  shipAddress: string; \n  freight: number; \n} \nexport interface OrderDetail { \n  orderId: number; \n  productId: number; \n  quantity: number; \n  unitPrice: number; \n} <\/pre>\n\n\n\n<p><strong>2. Create a Data Service<\/strong>&nbsp;<\/p>\n\n\n\n<p>Centralize all data-fetching logic in an Angular service, e.g., northwind-swagger.service.ts. Here\u2019s a sample service that interacts with a custom Northwind API, which we\u2019ll use in this example, and includes basic error handling:&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">const API_ENDPOINT = 'https:\/\/data-northwind.indigo.design'; \n@Injectable({ \n  providedIn: 'root' \n}) \nexport class NorthwindSwaggerService { \n  constructor(private http: HttpClient) {} \n  public getCustomerDto(id: string): Observable&lt;Customer | undefined> { \n    return this.http.get&lt;Customer | undefined>(`${API_ENDPOINT}\/Customers\/${id}`) \n      .pipe(catchError(this.handleError&lt;Customer | undefined>('getCustomerDto', undefined))); \n  } \n  public getCustomerDtoList(): Observable&lt;Customer[]> { \n    return this.http.get&lt;Customer[]>(`${API_ENDPOINT}\/Customers`) \n      .pipe(catchError(this.handleError&lt;Customer[]>('getCustomerDtoList', []))); \n  } \n  public getOrderWithDetailsDtoList(id: string): Observable&lt;Order[]> { \n    return this.http.get&lt;Order[]>(`${API_ENDPOINT}\/Customers\/${id}\/Orders\/WithDetails`) \n      .pipe(catchError(this.handleError&lt;Order[]>('getOrderWithDetailsDtoList', []))); \n  } \n  private handleError&lt;T>(operation = 'operation', result?: T) { \n    return (error: any): Observable&lt;T> => { \n      console.error(`${operation} failed: ${error.message}`, error); \n      return of(result as T); \n    }; \n  } \n} <\/pre>\n\n\n\n<p>With your models and service prepared, the grid components can now bind directly to arrays of orders per selected customer. From here, we\u2019ll move on to designing the Master Grid and progressively add detail views, custom templates, and performance optimizations.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"designing-the-master-grid\">Designing the Master Grid&nbsp;<\/h2>\n\n\n\n<p>Now that our data service is ready, it\u2019s time to build the master-detail UI step by step. We\u2019ll start by selecting a customer, displaying their orders in a master grid, and then enhancing the grid with a customizable detail template.&nbsp;<\/p>\n\n\n\n<p><strong>1. Adding a Combo to Select Customers<\/strong>&nbsp;<\/p>\n\n\n\n<p>To allow users to pick a customer, we\u2019ll use the igx-simple-combo component that asynchronously loads customer data from the service.&nbsp;<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>When a user selects a customer, we update a local variable localCustomerId in the component.&nbsp;<\/li>\n\n\n\n<li>This triggers fetching the corresponding orders for that customer.&nbsp;<\/li>\n<\/ul>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;igx-simple-combo  \n        type=\"border\"  \n        [data]=\"northwindSwaggerCustomerDto\"  \n        displayKey=\"customerId\"  \n        (selectionChanging)=\"localCustomerId = $event.newValue.customerId\"  \n         class=\"single-select-combo\"> \n    &lt;\/igx-simple-combo> <\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public northwindSwaggerCustomerDto: CustomerDto[] = []; \nprivate _localCustomerId?: string; \npublic get localCustomerId(): string | undefined { \n    return this._localCustomerId; \n  } \n  public set localCustomerId(value: string | undefined) { \n    this._localCustomerId = value; \n    this.selectedCustomer$.next(); \n    this.northwindSwaggerOrderWithDetailsDto$.next(); \n  } \n ngOnInit() { \nthis.northwindSwaggerService.getCustomerDtoList().pipe(takeUntil(this.destroy$)).subscribe( \n      data => this.northwindSwaggerCustomerDto = data \n    ); \n} <\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/staging.infragistics.com\/blogs\/wp-content\/uploads\/2025\/08\/master-detail-1.png\" alt=\"\" class=\"wp-image-2617\"\/><\/figure>\n\n\n\n<p><strong>2. Adding the Master Grid for Orders<\/strong>&nbsp;<\/p>\n\n\n\n<p>Once a customer is selected, we display their orders using an igx-grid. The grid\u2019s data source is northwindSwaggerOrderWithDetailsDto, updated every time localCustomerId changes.&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;igx-grid [data]=\"northwindSwaggerOrderWithDetailsDto\" primaryKey=\"orderId\" rowSelection=\"single\" [hideRowSelectors]=\"true\" [allowFiltering]=\"true\" filterMode=\"excelStyleFilter\" (rowSelectionChanging)=\"selectedOrder = $event.newSelection[0]\" class=\"grid\">\n      &lt;igx-column field=\"orderId\" oldDataType=\"number\" header=\"orderId\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"customerId\" oldDataType=\"string\" header=\"customerId\" [filterable]=\"true\" [sortable]=\"true\" required=\"true\" [minlength]=\"1\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"employeeId\" oldDataType=\"number\" header=\"employeeId\" [filterable]=\"true\" [sortable]=\"true\" [min]=\"1\" [max]=\"2147483647\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"shipperId\" oldDataType=\"number\" header=\"shipperId\" [filterable]=\"true\" [sortable]=\"true\" [min]=\"1\" [max]=\"2147483647\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"orderDate\" oldDataType=\"date\" header=\"orderDate\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"requiredDate\" oldDataType=\"date\" header=\"requiredDate\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"shipVia\" oldDataType=\"string\" header=\"shipVia\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"freight\" oldDataType=\"number\" header=\"freight\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"shipName\" oldDataType=\"string\" header=\"shipName\" [filterable]=\"true\" [sortable]=\"true\" [maxlength]=\"100\" [selectable]=\"false\">&lt;\/igx-column>\n      &lt;igx-column field=\"completed\" oldDataType=\"boolean\" header=\"completed\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n&lt;\/igx-grid><\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">private _selectedCustomer?: CustomerDto; \n  public get selectedCustomer(): CustomerDto | undefined { \n    return this._selectedCustomer; \n  } \n  public set selectedCustomer(value: CustomerDto | undefined) { \n    this._selectedCustomer = value; \n    this.selectedOrder = undefined; \n  } \n  public selectedCustomer$: Subject&lt;void> = new Subject&lt;void>(); \n  public northwindSwaggerOrderWithDetailsDto: OrderWithDetailsDto[] = []; \n  public northwindSwaggerOrderWithDetailsDto$: Subject&lt;void> = new Subject&lt;void>(); \n  public selectedOrder?: OrderWithDetailsDto; \n  ngOnInit() { \n    this.northwindSwaggerService.getOrderWithDetailsDtoList(this.localCustomerId ?? '').pipe(takeUntil(this.destroy$)).subscribe( \n      data => this.northwindSwaggerOrderWithDetailsDto = data \n    ); \n    this.northwindSwaggerOrderWithDetailsDto$.pipe(takeUntil(this.destroy$)).subscribe(() => { \n      this.northwindSwaggerService.getOrderWithDetailsDtoList(this.localCustomerId ?? '').pipe(take(1)).subscribe( \n        data => this.northwindSwaggerOrderWithDetailsDto = data \n      ); \n    }); \n  } <\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/staging.infragistics.com\/blogs\/wp-content\/uploads\/2025\/08\/master-detail-2.png\" alt=\"\" class=\"wp-image-2618\"\/><\/figure>\n\n\n\n<p><strong>3. Adding a Custom Detail Template<\/strong>&nbsp;<\/p>\n\n\n\n<p>To make the master grid more useful, we add a detail template for each order. This could contain product details, totals, or any related information. In our case, we\u2019ll be using it to display some of the grid\u2019s columns for address and additionally order details in a grid.&nbsp;<\/p>\n\n\n\n<p>To configure the\u202figxGrid\u202fto display in master-detail mode, you need to specify a template inside the grid, marked with the\u202figxGridDetail\u202fdirective:&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;ng-template igxGridDetail> &lt;\/ng-template><\/pre>\n\n\n\n<p>You can customize this detail template with any components that fit your business needs: text, input groups, nested grids, or charts. Here we\u2019ll use texts and a grid.&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;ng-template igxGridDetail let-rowData>\n        &lt;div class=\"row-layout group_3\">\n          &lt;div class=\"row-layout group_4\">\n            &lt;div class=\"column-layout group_5\">\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                Country\n              &lt;\/p>\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                Code\n              &lt;\/p>\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                City\n            &lt;\/p>\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                Street\n              &lt;\/p>\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                Phone\n              &lt;\/p>\n            &lt;\/div>\n            &lt;div class=\"column-layout group_6\">\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                {{ rowData.shipAddress.country }}\n              &lt;\/p>\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                {{ rowData.shipAddress.postalCode }}\n              &lt;\/p>\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                {{ rowData.shipAddress.city }}\n              &lt;\/p>\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                {{ rowData.shipAddress.street }}\n              &lt;\/p>\n              &lt;p class=\"ig-typography__subtitle-1 text\">\n                {{ rowData.shipAddress.phone }}\n              &lt;\/p>\n            &lt;\/div>\n          &lt;\/div>\n          &lt;div class=\"column-layout group_7\">\n            &lt;p class=\"text\">\n              Order Details\n            &lt;\/p>\n            &lt;igx-grid primaryKey=\"orderId\" [allowFiltering]=\"true\" filterMode=\"excelStyleFilter\" [data]=\"rowData.orderDetails\" class=\"grid_1\">\n              &lt;igx-column field=\"orderId\" oldDataType=\"number\" header=\"orderId\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n              &lt;igx-column field=\"productId\" oldDataType=\"number\" header=\"productId\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n              &lt;igx-column field=\"unitPrice\" oldDataType=\"number\" header=\"unitPrice\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n              &lt;igx-column field=\"quantity\" oldDataType=\"number\" header=\"quantity\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n              &lt;igx-column field=\"discount\" oldDataType=\"number\" header=\"discount\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column>\n            &lt;\/igx-grid>\n          &lt;\/div>\n        &lt;\/div>\n      &lt;\/ng-template><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/staging.infragistics.com\/blogs\/wp-content\/uploads\/2025\/08\/master-detail-3.png\" alt=\"\" class=\"wp-image-2620\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"handling-data-binding\">Handling Data Binding&nbsp;<\/h2>\n\n\n\n<p>The context of the template is the master record data, so that values from the master record can be displayed in the detail template.&nbsp;<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The context is the master record that the detail belongs to.&nbsp;<\/li>\n\n\n\n<li>You declare a template context variable using the let- syntax, which lets you access the master record\u2019s data inside the detail template.&nbsp;<\/li>\n\n\n\n<li>In our example, we use let-rowData to name this context variable.&nbsp;<\/li>\n<\/ul>\n\n\n\n<p>This means inside the detail template, you can access any property of the master record through rowData. For instance, rowData.shipAddress.country and rowData.orderDetails&nbsp;accesses the order\u2019s ID from the master grid\u2019s record.&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">      &lt;ng-template igxGridDetail let-rowData> \n            &lt;div class=\"column-layout group_6\"> \n              &lt;p class=\"ig-typography__subtitle-1 text\"> \n                {{ rowData.shipAddress.country }} \n              &lt;\/p> \n          &lt;div class=\"column-layout group_7\"> \n            &lt;igx-grid primaryKey=\"orderId\" [allowFiltering]=\"true\" filterMode=\"excelStyleFilter\" [data]=\"rowData.orderDetails\" class=\"grid_1\">\n             \/igx-grid> \n          &lt;\/div> \n        &lt;\/div> \n      &lt;\/ng-template><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"simple-dynamic-loading\">Simple Dynamic Loading&nbsp;<\/h2>\n\n\n\n<p>In many cases, you don\u2019t want to load all the detail data up front. Instead, you can fetch it on demand when a row is expanded.&nbsp;<\/p>\n\n\n\n<p>Sometimes the detail data comes from a completely different endpoint and requires a parameter from the master record (for example, an order\u2019s ID). In such cases, we can load the detail on demand when the row is expanded.&nbsp;<\/p>\n\n\n\n<p>In this approach, every time a row is expanded, we request the detail data from the API using the master record\u2019s ID.&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;igx-grid [data]=\"northwindSwaggerOrderDto\" primaryKey=\"orderId\" [allowFiltering]=\"true\" filterMode=\"excelStyleFilter\" class=\"grid\" (rowToggle)=\"onRowToggle($event)\"> \n    &lt;igx-column field=\"orderId\" dataType=\"number\" header=\"orderId\" [filterable]=\"true\" [sortable]=\"true\" [selectable]=\"false\">&lt;\/igx-column> \n    &lt;ng-template igxGridDetail let-rowData> \n      &lt;ng-container *ngIf=\"getOrderDetails(rowData.orderId) | async as details\"> \n        @for (item of details; track item) { \n          &lt;igx-input-group type=\"border\" class=\"input\"> \n            &lt;input type=\"text\" [(ngModel)]=\"item.orderId\" igxInput \/> \n          &lt;\/igx-input-group> \n        }\n      &lt;\/ng-container> \n    &lt;\/ng-template> \n  &lt;\/igx-grid><\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"typescript\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">getOrderDetails(orderId: number): Observable&lt;any[]> { \n      return this.northwindSwaggerService \n    .getOrderDetailDtoList(orderId) \n    .pipe( \n      take(1) \n    );   \n}<\/pre>\n\n\n\n<p>While straightforward, this approach triggers a new API call constantly after it\u2019s been made visible. This could slow down the application and even cause it to crash.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"performance-optimization-tips\">Performance Optimization Tips&nbsp;<\/h2>\n\n\n\n<p>Repeatedly fetching the same detail data is costly in both performance and network usage. A simple and effective solution is to cache the data after it\u2019s loaded for the first time.&nbsp;<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">private orderDetailsCache = new Map&lt;number, Observable&lt;any[]>>(); \ngetOrderDetails(orderId: number): Observable&lt;any[]> { \n    if (!this.orderDetailsCache.has(orderId)) { \n      const request$ = this.northwindSwaggerService \n        .getOrderDetailDtoList(orderId) \n        .pipe( \n          take(1), \n          shareReplay(1), \n        ); \n      this.orderDetailsCache.set(orderId, request$); \n    } \n    return this.orderDetailsCache.get(orderId)!; \n  }<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"how-does-it-work\">How Does It Work?&nbsp;<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>On the first expansion of a row, getOrderDetails() calls the API and stores the resulting observable in orderDetailsCache.&nbsp;<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>This method will be called constantly, but because the method returns the cached observable, it prevents duplicate requests.&nbsp;<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The shareReplay(1) operator ensures that even if subscribers attach after the API call has completed, they receive the cached data immediately without triggering a new request.&nbsp;<\/li>\n<\/ul>\n\n\n\n<p>This is easy to implement and significantly reduces the number of requests.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"wrap-up\">Wrap Up&#8230;&nbsp;<\/h2>\n\n\n\n<p>Building Master-Detail views in Angular doesn\u2019t have to be complex or performance-intensive. With Ignite UI for Angular, you gain a powerful, elegant solution that requires minimal setup yet offers full flexibility and customization to fit your application\u2019s unique needs.&nbsp;<\/p>\n\n\n\n<p>Whether you are building admin dashboards, management systems, or data-driven interfaces, Ignite UI\u2019s Angular grid gives you everything you need to develop fast, responsive, and maintainable applications.<\/p>\n\n\n\n<p>You can check out our <a href=\"https:\/\/www.infragistics.com\/products\/ignite-ui-angular\/grid-samples\">Ignite UI for Angular Grid samples<\/a> and explore all the components and features used. <span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">The&nbsp;<a href=\"https:\/\/www.infragistics.com\/resources\/sample-applications\/fleet-management-app\" target=\"_blank\">Fleet Management app<\/a>&nbsp;demonstrates the use of Master-Detail Grid, so you can see how the data is presented and managed in real-world scenarios.<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The master-detail layout is a proven UI pattern that provides a straightforward, scalable, and user-friendly way to present related data without overwhelming the screen.\u00a0Learn more in this article.<\/p>\n","protected":false},"author":173,"featured_media":3032,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[23,48,36,20,38],"class_list":["post-3029","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-angular","tag-angular","tag-angular-grids","tag-app-development","tag-ignite-ui","tag-ignite-ui-angular"],"_links":{"self":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/3029","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/users\/173"}],"replies":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/comments?post=3029"}],"version-history":[{"count":13,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/3029\/revisions"}],"predecessor-version":[{"id":3113,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/3029\/revisions\/3113"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/media\/3032"}],"wp:attachment":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/media?parent=3029"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/categories?post=3029"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/tags?post=3029"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}