Это потому, что вы перезаписываете свой список пользователей, а не добавляете новых. Очень простым решением было бы добавить пользователю «дочернее» поле, а затем выполнить что-то вроде
<ul>
<li *ngFor="let user of list">
<button (click)="onClick(user)"> {{user.name}}</button>
<ul *ngIf="user.children != null">
<li *ngFor="let child of user.children">{{child.name}}</li>
</ul>
</li>
</ul>
Где onClick добавляет загруженных пользователей в дочернее поле
onClick(user) {
this.dataService.getUsers(user.id).subscribe(x => user.children = x);
}
If вы также добавляете «расширенное» поле, а затем разворачиваете / свертываете дочерние узлы на основе этого, у вас есть простое функциональное дерево.
Это очень простое решение, но не идеальное. Ваша идея с использованием дерева матов хороша, но на удивление сложна.
Вам нужно будет определить узел вашего дерева
export class StackOverflowNode {
constructor(public item: User, public level = 1, public expandable = false,
public isLoading = false) {}
}
определить источник данных
export class StackOverflowDataSource implements DataSource<StackOverflowNode> {
dataChange = new BehaviorSubject<StackOverflowNode[]>([]);
get data(): StackOverflowNode[] { return this.dataChange.value; }
set data(value: StackOverflowNode[]) {
this._treeControl.dataNodes = value;
this.dataChange.next(value);
}
constructor(private _treeControl: FlatTreeControl<StackOverflowNode>,
private _service: UserService) {}
connect(collectionViewer: CollectionViewer): Observable<StackOverflowNode[]> {
this._treeControl.expansionModel.changed.subscribe(change => {
if ((change as SelectionChange<StackOverflowNode>).added ||
(change as SelectionChange<StackOverflowNode>).removed) {
this.handleTreeControl(change as SelectionChange<StackOverflowNode>);
}
});
return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data));
}
disconnect(collectionViewer: CollectionViewer): void {}
/** Handle expand/collapse behaviors */
handleTreeControl(change: SelectionChange<StackOverflowNode>) {
if (change.added) {
change.added.forEach(node => this.toggleNode(node, true));
}
if (change.removed) {
change.removed.slice().reverse().forEach(node => this.toggleNode(node, false));
}
}
/**
* Toggle the node, remove from display list
*/
toggleNode(node: StackOverflowNode, expand: boolean) {
node.isLoading = true;
this._service.getUsers(node.item.id).subscribe(children => {
const index = this.data.indexOf(node);
if (!children || index < 0) { // If no children, or cannot find the node, no op
return;
}
if (expand) {
const nodes = children.map(child =>
new StackOverflowNode(child, node.level + 1, false));
this.data.splice(index + 1, 0, ...nodes);
} else {
let count = 0;
for (let i = index + 1; i < this.data.length
&& this.data[i].level > node.level; i++, count++) {}
this.data.splice(index + 1, count);
}
// notify the change
this.dataChange.next(this.data);
node.isLoading = false;
});
}
И используйте это в своем компоненте
@Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss']
})
export class SidebarComponent {
treeControl: FlatTreeControl<StackOverflowNode>;
dataSource: StackOverflowDataSource;
getLevel = (node: StackOverflowNode) => node.level;
isExpandable = (node: StackOverflowNode) => node.expandable;
hasChild = (_: number, _nodeData: StackOverflowNode) => _nodeData.expandable
constructor(private dataService: DataService) {
this.treeControl = new FlatTreeControl<StackOverflowNode>(this.getLevel, this.isExpandable);
this.dataSource = new StackOverflowDataSource(this.treeControl, this.userService);
this.dataService.getUsers().subscribe(x => this.dataSource.data = x.map(data => new StackOverflowNode(data,0,true,false)))
}
}
И используйте его в своем html
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<button mat-icon-button disabled></button>
{{node.item.name}}
</mat-tree-node>
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
<button mat-icon-button
[attr.aria-label]="'toggle ' + node.filename" matTreeNodeToggle>
<mat-icon class="mat-icon-rtl-mirror">
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>
{{node.item.name}}
<mat-progress-bar *ngIf="node.isLoading"
mode="indeterminate"
class="example-tree-progress-bar"></mat-progress-bar>
</mat-tree-node>
</mat-tree>
Это было адаптировано для ваших источников данных из https://material.angular.io/components/tree/examples. Вы можете изучить эту страницу, чтобы увидеть, как работает этот код. Обратите внимание, что для простоты я сделал предположение, что ваш список имеет только 2 уровня - вместо того, чтобы фактически проверять, есть ли у узла дочерние элементы, я автоматически предполагаю, что узлы уровня 0 имеют, а узлы уровня 1 - нет.