The problem involves implementing a dynamic and potentially infinitely recursive nested table structure in an Angular application using the DevExtreme Master-Detail component. The main challenges include:
Dynamic Data: The data structure for the tables is dynamic, meaning the columns and rows can differ every time the data is loaded. This requires a flexible approach to generating table structures and content.
Variable Nesting: Some rows may contain nested tables, while others do not. Furthermore, the depth of nesting can vary, with some rows containing multiple levels of nested tables.
I need to create a recursive Angular component or find a solution to be capable of rendering a table with nested tables based on the provided data, handling the dynamic creation of columns, and ensuring that the nested data is correctly displayed and managed using the Master-Detail component of DevExtreme.
here is my current implementation. but the problem with this solution is that I create a new component for each level of nesting and pass the data.
**Main Table : **
import { Component} from '@angular/core';
@Component({
selector: 'app-hierachy-table',
template:`<dx-data-grid id="gridContainer" [dataSource]="employees" keyExpr="ID" [showBorders]="true" [allowColumnReordering]="true">
<dxo-master-detail [enabled]="true" [template]="employees"></dxo-master-detail>
<div *dxTemplate="let employee of employees">
<employee-detail [key]="employee.key"></employee-detail>
</div>
</dx-data-grid>`,
})
export class HierachyTableComponent
{
employees: any = [{
ID: 1,
Prefix: 'Mr.',
FirstName: 'John',
LastName: 'Heart',
Position: ['CEO', 'CTFO'],
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',
}];
constructor() { }
}
First Level Nesting Component:
@Component({
selector: 'employee-detail',
template: `<dx-data-grid [dataSource]="tasksDataSource" [showBorders]="true" [columnAutoWidth]="true">
<dxo-master-detail [enabled]="true" [template]="employeeDetails"></dxo-master-detail>
<div *dxTemplate="let employee of employeeDetails">
<employee-development [key]="employee.key"></employee-development>
</div>
</dx-data-grid>`
})
export class DetailGridComponent implements AfterViewInit
{
public tasksDataSource: any;
@Input()
public key: number | any;
employeeDetails: any = [
{
EmployeeID: 1,
EmploymentYear: '2012',
JoinDate: '2012/04/10',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Active',
ProjectsCompleted: 120,
},
{
EmployeeID: 2,
EmploymentYear: '2012',
JoinDate: '2014/05/20',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Inactive',
ProjectsCompleted: 60,
},
{
EmployeeID: 3,
EmploymentYear: '2012',
JoinDate: '2020/06/15',
ContractEnd: '2100/12/31',
EmploymentStatus: 'On Leave',
ProjectsCompleted: 30,
},
{
EmployeeID: 4,
EmploymentYear: '2012',
JoinDate: '2006/07/25',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Active',
ProjectsCompleted: 450,
},
{
EmployeeID: 5,
EmploymentYear: '2012',
JoinDate: '2012/08/05',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Active',
ProjectsCompleted: 110,
},
{
EmployeeID: 6,
EmploymentYear: '2012',
JoinDate: '2012/09/15',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Active',
ProjectsCompleted: 95,
},
{
EmployeeID: 7,
EmploymentYear: '2012',
JoinDate: '2012/10/25',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Active',
ProjectsCompleted: 105,
},
{
EmployeeID: 8,
EmploymentYear: '2012',
JoinDate: '2012/11/05',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Active',
ProjectsCompleted: 92,
},
{
EmployeeID: 9,
EmploymentYear: '2012',
JoinDate: '2012/12/15',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Active',
ProjectsCompleted: 88,
},
{
EmployeeID: 10,
EmploymentYear: '2012',
JoinDate: '2012/04/10',
ContractEnd: '2100/12/31',
EmploymentStatus: 'Active',
ProjectsCompleted: 115,
}
];
ngAfterViewInit()
{
this.tasksDataSource = new DataSource({
store: new ArrayStore({
data: this.employeeDetails,
key: 'EmployeeID',
}),
filter: ['EmployeeID', '=', this.key],
});
}
}
third Level Nesting:
@Component({
selector: 'employee-development',
template: `<dx-data-grid [dataSource]="dataSource" [showBorders]="true" [columnAutoWidth]="true">
<dxo-master-detail [enabled]="true" [template]="employeeDevelopments"></dxo-master-detail>
<div *dxTemplate="let development of employeeDevelopments">
<employee-compansation [key]="development.key"></employee-compansation>
</div>
</dx-data-grid>`})
export class EmployeeDevelopmentComponent
{
@Input()
key: any;
employeeDevelopments: any = [
{
EmployeeID: 1,
TrainingSessionsAttended: 5,
SkillsAcquired: ['Project Management', 'Time Management', 'Advanced Excel'],
LastPerformanceReviewScore: 9,
NextReviewDate: '2024/04/10',
},
{
EmployeeID: 2,
TrainingSessionsAttended: 3,
SkillsAcquired: ['Communication', 'Critical Thinking'],
LastPerformanceReviewScore: 7,
NextReviewDate: '2024/05/20',
},
{
EmployeeID: 3,
TrainingSessionsAttended: 4,
SkillsAcquired: ['Team Leadership', 'Conflict Resolution', 'Public Speaking'],
LastPerformanceReviewScore: 8,
NextReviewDate: '2024/06/15',
},
{
EmployeeID: 4,
TrainingSessionsAttended: 6,
SkillsAcquired: ['Strategic Planning', 'Budget Management', 'Negotiation'],
LastPerformanceReviewScore: 9.5,
NextReviewDate: '2024/07/25',
},
{
EmployeeID: 5,
TrainingSessionsAttended: 2,
SkillsAcquired: ['Data Analysis', 'Critical Thinking'],
LastPerformanceReviewScore: 8.5,
NextReviewDate: '2024/08/05',
},
{
EmployeeID: 6,
TrainingSessionsAttended: 3,
SkillsAcquired: ['Creative Problem Solving', 'Effective Communication'],
LastPerformanceReviewScore: 7.5,
NextReviewDate: '2024/09/15',
},
{
EmployeeID: 7,
TrainingSessionsAttended: 4,
SkillsAcquired: ['Project Management', 'Team Motivation', 'Agile Methodologies'],
LastPerformanceReviewScore: 8,
NextReviewDate: '2024/10/25',
},
{
EmployeeID: 8,
TrainingSessionsAttended: 2,
SkillsAcquired: ['Time Management', 'Stress Management'],
LastPerformanceReviewScore: 7,
NextReviewDate: '2024/11/05',
},
{
EmployeeID: 9,
TrainingSessionsAttended: 5,
SkillsAcquired: ['Digital Marketing', 'SEO Basics', 'Content Creation'],
LastPerformanceReviewScore: 8.5,
NextReviewDate: '2024/12/15',
},
{
EmployeeID: 10,
TrainingSessionsAttended: 3,
SkillsAcquired: ['Analytical Thinking', 'Data Presentation'],
LastPerformanceReviewScore: 9,
NextReviewDate: '2024/04/10',
}
];
dataSource: any;
ngAfterViewInit()
{
this.dataSource = new DataSource({
store: new ArrayStore({
data: this.employeeDevelopments,
key: 'EmployeeID',
}),
filter: ['EmployeeID', '=', this.key],
});
}
}
I am expecting to have a component where it renders the nesting automatically. instead of creating new component for each level of nesting.