I'm working on an Angular project where I have a MatTable, and I want to display a nested table when clicking the "Add" button (toggle button). However, the nested table is not displaying as expected.
Code Snippets:
This is my html file
<div class="mainHeader text-center">Search By OUID</div>
<div class="mainContainer">
<div class="container">
<form #ouidForm="ngForm">
<div class="form-group">
<label for="searchouid" class="labelstyle">Enter OUID</label>
<div class="search-container d-flex align-items-start">
<div class="input-group">
<input #searchInput="ngModel" type="text" class="form-control" id="searchouid"
aria-label="Search by OUID" placeholder="Please Enter OUID" [(ngModel)]="ouid" name="ouid"
required [class.is-invalid]="searchInput.invalid && searchInput.touched">
<div *ngIf="searchInput.touched && searchInput.invalid" class="invalid-feedback">
OUID is Required
</div>
</div>
<button (click)="searchOUID(searchInput.value, $event)" class="btnRounded"
[disabled]="searchInput.invalid || isSearching">Search</button>
</div>
</div>
</form>
</div>
<div *ngIf="showForm">
<hr>
<div class="refreshbutton">
<button type="button" class="btnOutlined" [disabled]="dataSource.data.length <= 1" (click)="splitData()">
<span class="bi bi-diagram-2"></span> Split
</button>
<button type="button" class="btnOutlined" (click)="refreshData()"><span class="icon ol-refresh"></span>
Refresh</button>
</div>
<div class="inner-container bg-light">
<div class="d-flex justify-content-between align-items-center px-4 mb-2">
<div class="d-flex align-items-center">
<i class="bi bi-info-square mr-2"></i>
<div class="mb-0">Basic Details</div>
</div>
<button class="toggle" (click)="toggleForm()">
{{ showbasicdetail ? '-' : '+' }}
</button>
</div>
</div>
<div *ngIf="showbasicdetail" class="details-container mb-4">
<!-- Group One -->
<div class="details-group px-4 py-3">
<div class="details-item">
<label for="status" class="labelstyle">OUID</label>
<p>{{ data.ouid }}</p>
</div>
<div class="details-item">
<label for="ouid" class="labelstyle">Status</label>
<p>{{ data.status }}</p>
</div>
<div class="details-item">
<label for="structureType" class="labelstyle">Structure Type</label>
<p>{{ data.structureType }}</p>
</div>
<div class="details-item">
<label for="locationAccuracy" class="labelstyle">Location Accuracy</label>
<p>{{ data.locationAccuracy }}</p>
</div>
</div>
<!-- Group Two -->
<div class="details-group px-4">
<div class="details-item">
<label for="trueLocation" class="labelstyle">Latitude</label>
<p>{{ data.trueLocation.latitude }}</p>
</div>
<div class="details-item">
<label for="trueLocation" class="labelstyle">Longitude</label>
<p>{{ data.trueLocation.longitude }}</p>
</div>
</div>
</div>
</div>
<div class="ag-grid-container-systemrefrences" *ngIf="showForm">
<div class="inner-containersys bg-light">
<div class="d-flex justify-content-between align-items-center px-4 ">
<div class="d-flex align-items-center">
<i class="bi bi-gear mr-2"></i>
<div class="mb-0">System References Details</div>
</div>
<button class="toggle" (click)="toggle()">
{{ showsystemreferences ? '-' : '+' }}
</button>
</div>
</div>
<div class="table-responsive" *ngIf="showsystemreferences">
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element; let i = index">
<input type="radio" name="selectedRow" [value]="element" [id]="'row-' + i">
</td>
</ng-container>
<ng-container matColumnDef="systemType">
<th mat-header-cell *matHeaderCellDef mat-sort-header>System Type</th>
<td mat-cell *matCellDef="let element"> {{element.systemType}} </td>
</ng-container>
<ng-container matColumnDef="structureType">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Structure Type </th>
<td mat-cell *matCellDef="let element"> {{element.structureType}} </td>
</ng-container>
<ng-container matColumnDef="location_accuracy">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Location Accuracy</th>
<td mat-cell *matCellDef="let element"> {{element.location_accuracy}} </td>
</ng-container>
<ng-container matColumnDef="latitude">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>
<td mat-cell *matCellDef="let element"> {{element.latitude}} </td>
</ng-container>
<ng-container matColumnDef="longitude">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>
<td mat-cell *matCellDef="let element"> {{element.longitude}} </td>
</ng-container>
<ng-container matColumnDef="system_ref_id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>SystemRefID</th>
<td mat-cell *matCellDef="let element"> {{element.system_ref_id}} </td>
</ng-container>
<ng-container matColumnDef="other">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Other</th>
<td mat-cell *matCellDef="let element"> {{element.other}} </td>
</ng-container>
<ng-container matColumnDef="toggleDetail">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button (click)="toggleNestedTable(element)">
<mat-icon>{{element.isExpanded ? 'remove' : 'add'}}</mat-icon>
</button>
</td>
</ng-container>
<!-- Main Table Header Row -->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<ng-container *ngFor="let element of dataSource.data">
<tr mat-row *matRowDef="let element; columns: displayedColumns;"></tr>
<!-- Nested Table Row -->
<tr class="nested-row" *ngIf="element.isExpanded === true">
<td [attr.colspan]="displayedColumns.length">
<table mat-table [dataSource]="element.nestedDataSource" class="inner-table">
<!-- Nested Table Columns -->
<ng-container *ngFor="let key of element.nestedKeys" [matColumnDef]="key">
<th mat-header-cell *matHeaderCellDef> {{ key | titlecase }}</th>
<td mat-cell *matCellDef="let nestedElement"> {{ nestedElement[key] }} </td>
</ng-container>
<!-- Nested Table Header and Rows -->
<tr mat-header-row *matHeaderRowDef="element.nestedKeys"></tr>
<tr mat-row *matRowDef="let nestedRow; columns: element.nestedKeys;"></tr>
</table>
</td>
</tr>
</ng-container>
</table>
</div>
</div>
</div>
This is my typescript file
import { Component, OnInit, OnDestroy } from '@angular/core';
import { OsiApiService } from '../../../../../shared/services/osi-api.service';
import { ViewChild } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { AlertHandlerService } from '../../../../../shared/services/alert-handler.service';
import { NgForm } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { debounceTime } from 'rxjs/operators';
import { Subscription } from 'rxjs';
import { ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'app-search-by-ouid',
templateUrl: './search-by-ouid.component.html',
styleUrls: ['./search-by-ouid.component.scss']
})
export class SearchbyOUIDComponent implements OnInit, OnDestroy {
@ViewChild('ouidForm') ouidForm!: NgForm;
ouid: string = '';
showForm = false;
showbasicdetail = true;
showsystemreferences = true;
data: any;
rowDataForSystemReferences: any[] = [];
dataSource = new MatTableDataSource<any>([]);
displayedColumns: string[] = ['select', 'systemType', 'structureType', 'latitude', 'longitude', 'location_accuracy', 'toggleDetail',];
isSearching: boolean = false;
@ViewChild(MatSort) sort!: MatSort;
apiresult: boolean = true;
constructor(private changeDetectorRef: ChangeDetectorRef, private osiApiService: OsiApiService, private alerthandler: AlertHandlerService, private router: Router, private route: ActivatedRoute) { }
private queryParamsSubscription!: Subscription;
ngOnInit(): void {
this.queryParamsSubscription = this.route.queryParams.subscribe(params => {
if (params['ouid']) {
if (this.apiresult) {
this.ouid = params['ouid'];
this.searchOUID(this.ouid);
}
}
});
}
toggleForm() {
this.showbasicdetail = !this.showbasicdetail;
}
toggle() {
this.showsystemreferences = !this.showsystemreferences;
}
searchOUID(ouid: string, event?: Event): void {
if (event) {
event.stopPropagation();
}
if (this.ouidForm && this.ouidForm.invalid) {
return;
}
if (this.isSearching) return;
this.isSearching = true;
this.osiApiService.searchByOUID(ouid).subscribe(response => {
console.log('API Response:', response);
// this.processData(response);
if (response && response.systemRefs && Array.isArray(response.systemRefs)) {
this.rowDataForSystemReferences = response.systemRefs.map((ref: any) => {
const systemRefId = ref.systemRefId || {};
const location = ref.location || {};
const nestedKeys = ref.systemRefId ? Object.keys(ref.systemRefId) : [];
// Create an object with the column names as keys and their corresponding values
const nestedData = nestedKeys.map(key => ({
key: key,
value: ref.systemRefId[key] || '' // Use empty string as a default value if the key doesn't exist
}));
const nestedDataSource = new MatTableDataSource(nestedData);
return {
systemType: ref.systemType,
ouid: response.ouid,
local_id: systemRefId.ServiceId,
source_system: systemRefId.ServiceDbId,
latitude: `${location.latitude}`,
longitude: `${location.longitude}`,
location_accuracy: response.locationAccuracy,
system_ref_id: JSON.stringify(systemRefId),
structureType: ref.structureType,
isExpanded: false, // This will be used for column definitions in the nested table
nestedData,
nestedKeys,
nestedDataSource,
};
});
this.dataSource = new MatTableDataSource(this.rowDataForSystemReferences);
this.dataSource.sort = this.sort;
this.data = response;
this.showForm = true;
this.apiresult = false;
this.router.navigate([], {
queryParams: { ouid: ouid },
queryParamsHandling: 'merge',
replaceUrl: true
});
this.isSearching = false;
}
}, error => {
if (error.status === 404) {
this.ouidModal();
} else {
this.errorModal();
}
this.isSearching = false;
});
}
toggleNestedTable(element: any): void {
console.log("Before toggle:", element.isExpanded);
element.isExpanded = !element.isExpanded;
console.log("After toggle:", element.isExpanded);
this.changeDetectorRef.detectChanges();
console.log("element.nestedKeys:", element.nestedKeys);
console.log("element.nestedData:", element.nestedData);
console.log(this.dataSource.data);
}
splitData(): void {
console.log("Split operation to be implemented");
}
ouidModal() {
this.alerthandler.showOuidNotFoundModal();
}
errorModal() {
this.alerthandler.showErrorModal();
}
refreshData(): void {
if (this.ouid) {
this.searchOUID(this.ouid);
} else {
console.log("refreshed successfully");
}
}
ngOnDestroy(): void {
if (this.queryParamsSubscription) {
this.queryParamsSubscription.unsubscribe();
}
}
}
Data Structure: I'm populating the MatTable with data from an API response. The nested data should be displayed within the table rows when expanded.
Steps Taken:
Checked the API response, and the data seems to be correctly formatted. Added console.log statements to debug the issue. Removed ChangeDetectorRef.detectChanges() to test if it affects the behavior. Expected Outcome: When I click the "Add" button, I expect the nested table to display beneath the corresponding row in the main table.
Actual Outcome: The nested table is not displaying, and I'm not receiving any error messages in the console.
Error Messages: No error messages are displayed in the console.