我正在使用Angular Material v6。我正在尝试从服务器上的JSON文件动态创建树。当前,树可以创建父级,但不能创建子级。控制台正确输出父级和子级。但是,我似乎找不到此问题的根源。
这是我的代码:
testcasefolder-list.component.html:
<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl" class="example-tree">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
<li class="mat-tree-node">
<button mat-icon-button disabled></button>
{{node.category.name}}
</li>
</mat-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
<li>
<div class="mat-tree-node">
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.filename">
<mat-icon class="mat-icon-rtl-mirror">
{{nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
{{node.category.name}}
</div>
<ul [class.example-tree-invisible]="!nestedTreeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</li>
</mat-nested-tree-node>
</mat-tree>
testcasefolder-list.component.ts:
import { TestcasefolderService } from './../../services/testcasefolder-service';
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { NestedTreeControl } from '@angular/cdk/tree';
import { MatTreeNestedDataSource } from '@angular/material';
import { of } from 'rxjs';
import { ITestcasefolder } from '../../model/itestcasefolder';
export class CategoryNode {
children: CategoryNode[ ];
category: ITestcasefolder;
}
@Component({
selector: 'app-testcasefolder-list',
templateUrl: './testcasefolder-list.component.html',
styleUrls: ['./testcasefolder-list.component.css']
})
export class TestcasefolderListComponent implements OnInit {
// Does not follow main/sub category - just has a simple category!
@Input() categoryID: number;
@Output() categoryIDChange: EventEmitter<number> = new EventEmitter<number>();
nestedTreeControl: NestedTreeControl<CategoryNode>;
nestedDataSource: MatTreeNestedDataSource<CategoryNode>;
type: any;
hasNestedChild = (_: number, nodeData) => !(nodeData.type);
constructor(
private dataBrokerService: TestcasefolderService
) {
this.nestedTreeControl = new NestedTreeControl(node => of(node.children));
this.nestedDataSource = new MatTreeNestedDataSource();
}
ngOnInit() {
this.dataBrokerService.getTestcasefolders().subscribe(categories => this.nestedDataSource.data = this.buildTree(categories));
}
onChange() {
this.categoryIDChange.emit(this.categoryID);
}
private buildTree(categories: ITestcasefolder[]): CategoryNode[] {
const tree: CategoryNode[] = [];
const map: {[s: number]: CategoryNode} = {};
// Build an index of the nodes
categories.forEach(cat => {
map[cat.id] = {children: [], category: cat};
});
console.log(tree);
// Start adding nodes to tree
for (const key of Object.keys(map)) {
const catNode = map[+key];
if (map[catNode.category.parentid]) {
// Add it to the parent
map[catNode.category.parentid].children.push(catNode);
} else {
// Add it to root
tree.push(catNode);
}
}
console.log(tree);
return tree;
}
}
testcasefolder-service.ts:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { catchError, map, tap } from 'rxjs/operators';
import { of, Observable } from 'rxjs';
import { MessageService } from './message-service.service';
import { ITestcasefolder } from '../model/itestcasefolder';
import { Global } from '../shared/Global';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable({
providedIn: 'root'
})
export class TestcasefolderService {
private testcasefolderServiceUrl = Global.BASE_USER_ENDPOINT + 'testcasefolders';
constructor(private http: HttpClient, private messageService: MessageService) { }
private log(message: string) {
this.messageService.add(`TestcasefolderService: ${message}`);
}
getTestcasefolders(): Observable<ITestcasefolder[]> {
return this.http.get<ITestcasefolder[]>(this.testcasefolderServiceUrl)
.pipe(
tap(requirements => this.log('fetched testcasefolder')),
catchError(this.handleError('getTestcasefolders', []))
);
}
getTestcasefolder(id: string): Observable<ITestcasefolder> {
const url = `${this.testcasefolderServiceUrl}/${id}`;
return this.http.get<ITestcasefolder>(url).pipe(
tap(_ => this.log(`fetched testcasefolder id=${id}`)),
catchError(this.handleError<ITestcasefolder>(`get testcasefolder id=${id}`))
);
}
addTestcasefolder(testcasefolder: ITestcasefolder): Observable<ITestcasefolder> {
delete testcasefolder['id'];
return this.http.post<ITestcasefolder>(this.testcasefolderServiceUrl, testcasefolder, httpOptions)
// tslint:disable-next-line:no-shadowed-variable
.pipe(tap((testcasefolder: ITestcasefolder) => this.log(`added testcasefolder w/ id= ${testcasefolder.id}`)),
catchError(this.handleError<ITestcasefolder>('addTestcasefolder'))
);
}
updateTestcasefolder(testcasefolder: ITestcasefolder): Observable<any> {
const url = `${this.testcasefolderServiceUrl}/${testcasefolder.id}`;
return this.http.put(url, testcasefolder, httpOptions)
.pipe(tap(_ => this.log(`updated testcasefolder id=${testcasefolder.id}`)),
catchError(this.handleError<any>('updateTestcasefolder')));
}
deleteTestcasefolder(testcasefolder: ITestcasefolder | string ): Observable<ITestcasefolder> {
const id = typeof testcasefolder === 'string' ? testcasefolder : testcasefolder.id;
const url = `${this.testcasefolderServiceUrl}/${id}`;
return this.http.delete<ITestcasefolder>(url, httpOptions)
.pipe(
tap(_ => this.log(`deleted testcasefolder`)),
catchError(this.handleError<ITestcasefolder>(`deletedTestcasefolder`))
);
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
}
itestcasefolder.ts:
export class ITestcasefolder {
id?: string;
name?: string;
description?: string;
parentid?: string;
children?: ITestcasefolder[];
}