Angular 4带单选按钮的树形表格(Tree Grid)实现方案咨询
Hey there! Let's work through your Angular 4 requirement for an expandable table/grid with radio buttons per item. Here are some practical options and implementation steps tailored to your nested JSON data structure:
Component Libraries (Quickest Solution)
Since you're using Angular 4, these libraries have stable support for that version and can handle expandable rows + radio buttons out of the box:
1. Angular Material (v2.x for Angular 4)
Angular Material's MatTable paired with MatExpansionPanel is a great fit. You can structure each row to include an expansion panel that reveals child items from the SubGroup array, plus add MatRadioButton for selection.
- How to set it up:
- Define your table columns, including a column for the radio button and a trigger to expand/collapse rows.
- Use
[(ngModel)]to bind all radio buttons to a single selected item variable, ensuring mutual exclusivity across all rows (parent and child). - Nest a smaller table or list inside the expansion panel to render the
SubGroupitems, repeating the radio button pattern for child entries.
2. PrimeNG (v4.x for Angular 4)
PrimeNG's TreeTable is built exactly for nested data structures like yours. It has built-in expand/collapse functionality, and you can easily customize columns to include radio buttons.
- Key perks:
- The component automatically parses your
SubGrouparray to build the hierarchical structure—no extra recursion needed. - Use the
p-radioButtoncomponent in a custom column template, set a sharednameattribute or bind to a single selection variable to ensure only one item is selected at a time.
- The component automatically parses your
Manual Implementation (Full Control)
If you prefer to avoid third-party libraries, you can build this from scratch using Angular's core directives:
Step 1: Prepare Your Data
First, add an isExpanded flag to each item (and child items) to track the expand/collapse state. You can do this when fetching data:
addExpansionFlag(items: any[]): any[] { return items.map(item => ({ ...item, isExpanded: false, SubGroup: item.SubGroup ? this.addExpansionFlag(item.SubGroup) : [] })); }
Step 2: Build the Template with Recursion
Use *ngFor and <ng-template> to render nested items recursively. Here's a simplified example:
<div class="table-container"> <div *ngFor="let item of data" class="table-row"> <!-- Parent Row --> <div class="row-content"> <input type="radio" name="itemSelection" [(ngModel)]="selectedItem" [value]="item" > <span class="expand-trigger" (click)="item.isExpanded = !item.isExpanded"> {{item.isExpanded ? '▼' : '▶'}} {{item.Name}} </span> <span class="description">{{item.Description}}</span> </div> <!-- Child Rows (Expanded) --> <div *ngIf="item.isExpanded && item.SubGroup.length" class="child-rows"> <ng-container *ngTemplateOutlet="childRowTemplate; context:{ $implicit: item.SubGroup }"></ng-container> </div> </div> </div> <!-- Recursive Template for Child Items --> <ng-template #childRowTemplate let-items> <div *ngFor="let subItem of items" class="table-row child-row"> <div class="row-content"> <input type="radio" name="itemSelection" [(ngModel)]="selectedItem" [value]="subItem" > <span class="expand-trigger" (click)="subItem.isExpanded = !subItem.isExpanded"> {{subItem.isExpanded ? '▼' : '▶'}} {{subItem.Name}} </span> <span class="description">{{subItem.Description}}</span> </div> <!-- Nested Children --> <div *ngIf="subItem.isExpanded && subItem.SubGroup.length" class="child-rows"> <ng-container *ngTemplateOutlet="childRowTemplate; context:{ $implicit: subItem.SubGroup }"></ng-container> </div> </div> </ng-template>
Step 3: Component Logic
In your component class, initialize the data and selected item variable:
import { Component } from '@angular/core'; // Don't forget to import FormsModule in your module! @Component({ selector: 'app-expandable-radio-table', templateUrl: './expandable-radio-table.component.html', styleUrls: ['./expandable-radio-table.component.css'] }) export class ExpandableRadioTableComponent { rawData = { "Id": 1, "Name": "India", "Description": "India", "ParentId": 0, "SubGroup": [ { "Id": 2, "Name": "Mumbai", "Description": "Mumbai", "ParentId": 1, "SubGroup": [] } ] }; data = this.addExpansionFlag([this.rawData]); selectedItem: any; addExpansionFlag(items: any[]): any[] { return items.map(item => ({ ...item, isExpanded: false, SubGroup: item.SubGroup ? this.addExpansionFlag(item.SubGroup) : [] })); } }
Step 4: Style It
Add CSS to make it look like a table—align the content, indent child rows, and style the expand trigger:
.table-container { border: 1px solid #ddd; border-radius: 4px; } .table-row { border-bottom: 1px solid #ddd; } .row-content { display: flex; padding: 10px; align-items: center; gap: 10px; } .child-row .row-content { padding-left: 30px; } .expand-trigger { cursor: pointer; font-weight: 500; } .description { margin-left: auto; color: #666; }
Key Notes
- Don't forget to import
FormsModulein your Angular module to use[(ngModel)]for radio buttons. - If your data comes from an API, modify the
addExpansionFlagmethod to process the incoming data before rendering. - For mutual exclusivity, ensure all radio buttons share the same
nameattribute (or bind to the samengModelvariable, which handles this automatically).
内容的提问来源于stack exchange,提问作者Naveen Suryawanshi

