问题
I am new to Angular (7) coming from a c# background. I am using asp.net core 2.2 and using the default template which comes with angular new angular project( home, counter, fetch-data). The tree view is bound and is coming from a controller.
I am expecting
> Europe
>> England
>>> Manchester
>>> London
>>>> London City
>>>> Stratford
>> Germany
however, i'm getting, every time i expand
> Europe
>> Europe
>>> Europe
>>>> Europe
and so on
my code (home page which shows as soon as ISS express is loaded)
<p>
Testing Componenets
<app-tree-view> </app-tree-view>
tree-view.component.html
<ul>
<li *ngFor="let n of nodes">
<span><input type="checkbox" [checked]="n.checked" (click)="n.toggle()" /></span>
{{ n.name }}>
<div *ngIf="n.expanded">
<app-tree-view [nodes]="n.nodes"></app-tree-view>
</div>
</li>
</ul>
tree-view.component.ts
import { Component, Inject, Input, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-tree-view',
templateUrl: './tree-view.component.html'
})
export class TreeViewComponent {
@Input() nodes: Array<Node>;
constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string) {
this.nodes = [];
http.get<Node[]>(baseUrl + 'api/FunctionNodes/GetNodes').subscribe(result => {
this.nodes = this.RecursiveMapNodes(result.map(x => new Node(x.id, x.name, x.nodes)));
}, error => console.error(error));
}
RecursiveMapNodes(nodes: Array<Node>): Array<Node> {
var result = Array<Node>();
for (let node of nodes) {
var n = new Node(node.id, node.name, this.RecursiveMapNodes(node.nodes));
result.push(n);
}
return result;
}
}
export class Node {
id: number;
name: string;
nodes: Array<Node>;
checked: boolean;
expanded: boolean;
constructor(id: number, name: string, nodes: Array<Node>) {
this.id = id;
this.name = name;
this.nodes = nodes;
this.checked = false;
this.expanded = false;
}
toggle() {
this.expanded = !this.expanded;
}
check() {
let newState = !this.checked;
this.checked = newState;
this.checkRecursive(newState);
}
checkRecursive(state) {
this.nodes.forEach(d => {
d.checked = state;
d.checkRecursive(state);
})
}
}
FunctionNodesController.cs
[Route("api/[controller]")]
public class FunctionNodesController : Controller
{
[HttpGet("[action]")]
public IEnumerable<Node> GetNodes()
{
var node_1 = new Node() { Id = 1, Name = "Europe" };
var node_1_1 = new Node() { Id = 2, Name = "England" };
var node_1_1_1 = new Node() { Id = 3, Name = "Manchester" };
var node_1_1_2 = new Node() { Id = 4, Name = "London" };
var node_2_1_1 = new Node() { Id = 5, Name = "London City" };
var node_2_1_2 = new Node() { Id = 6, Name = "Stratford" };
var node_1_2 = new Node() { Id = 7, Name = "Germany" };
node_1.Nodes.Add(node_1_1);
node_1_1.Nodes.Add(node_1_1_1);
node_1_1.Nodes.Add(node_1_1_2);
node_1_1_2.Nodes.Add(node_2_1_1);
node_1_1_2.Nodes.Add(node_2_1_2);
node_1.Nodes.Add(node_1_2);
return new List<Node>() { node_1 };
}
public class Node
{
public int Id { get; set; }
public string Name { get; set; }
public List<Node> Nodes { get; set; }
public Node()
{
Nodes = new List<Node>();
}
}
}
app.module.tss
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { CounterComponent } from './counter/counter.component';
import { FetchDataComponent } from './fetch-data/fetch-data.component';
import { MainWindowComponent } from './main-window/main-window.component';
import { TreeViewComponent } from './tree-view/tree-view.component';
@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
CounterComponent,
FetchDataComponent,
MainWindowComponent,
TreeViewComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
RouterModule.forRoot([
{ path: '', component: MainWindowComponent, pathMatch: 'full' },
{ path: 'counter', component: CounterComponent },
{ path: 'fetch-data', component: FetchDataComponent },
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
回答1:
Hello I have created a little demo that you can use as reference, for the problem that you are tackling, you can find it here at CodeSandbox.
As im not very knowledgeable about the C# part, I have created a mock back-end service that is supposed to act as your back-end.
About the question, why does it not work, as you had already mentioned in the comments, every time you are initializing (going a level in) your tree-view.component.ts
inside of it's constructor you are fetching the data, which results in always getting 'Europe' displayed as result.
When creating recursive elements (trees and etc.) you must always provide to the recursive component (in your case tree-view.component.ts
) the next layer of the tree.
For example first 'Europe' => ['England' => ['Manchester' , 'London' => ['London city', 'Stratford] ] , 'Germany'] , where each =>
is building new tree-view.component.ts
// Template
<div [ngStyle]="{'margin-left':level*12 +'px'}" *ngFor="let node of nodes">
<h1>Current node = {{node?.name}}</h1>
<div
(click)="open=!open"
style="cursor: pointer;"
*ngIf="node?.nodes?.length!==0"
>
Expand nodes
</div>
<div *ngIf="open">
<app-tree [level]="level+1" [nodesForDisplay]="node.nodes"></app-tree>
</div>
</div>
// Component
@Component({
selector: "app-tree",
templateUrl: "./tree.component.html"
})
export class TreeComponent implements OnInit {
@Input("nodesForDisplay") nodes;
@Input("level") level = 0;
open = false;
}
So in order to escape this deadlock, of always fetching the first layer, you can try creating a wrapper parent component, that handles the fetching and passes the data to the recursive components.
Another tip, is to remove the http call from the component and place it in a dedicated service, that you will inject inside of the components where needed (as done in the example)
// Template
<app-tree [nodesForDisplay]="{{place to strore the data}}"></app-tree>
// Component
@Component({
selector: "app-container",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class ContainerComponent {
...
constructor(public fetcher: {{Your Service}}) {}
ngOnInit() {
this.fetcher.{{method to fethc the data}}.subscribe(x =>{
this.{{place to strore the data}} = x
})
}
}
回答2:
i'm posting the code of the question and how it changed from the first post (FunctionNodesController.cs remains the same as original post), i don't know the best practices when it comes to angular so if you see something bad please leave a comment. (FunctionNodesController for this case can be renamed to RegionNodesController and similarly for FunctionService to RegionService)
tree-view.component.ts
import { Component, Input, } from '@angular/core';
import { TreeNode } from '../../classes/TreeNode';
@Component({
selector: 'app-tree-view',
templateUrl: './tree-view.component.html'
})
export class TreeViewComponent{
@Input() nodes: Array<TreeNode>;
constructor() {
}
}
tree-view.component.html
<ul *ngIf="nodes">
<li *ngFor="let n of nodes">
<span><input type="checkbox" (click)="n.toggle()" /></span>
{{ n.name }}
<div *ngIf="n.expanded">
<app-tree-view [nodes]="n.nodes"></app-tree-view>
</div>
</li>
</ul>
main-window.html
<app-tree-view [nodes]="nodes"> </app-tree-view>
main-window.ts
import { Component, Inject, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { TreeNode } from '../../classes/TreeNode';
import { FunctionService } from '../../services/functionService';
@Component({
selector: 'app-main-window',
templateUrl: './main-window.component.html',
})
export class MainWindowComponent{
nodes = [];
constructor(private funcService: FunctionService) {
funcService.getNodeData().subscribe(result => {
this.nodes = this.RecursiveMapNodes(result.map(x => new TreeNode(x.id, x.name, x.nodes)));
}, error => console.error(error));
}
<!--this is required as during serialisation the methods/ function in nodes are not instanced unless we create a new object-->
RecursiveMapNodes(nodes: Array<TreeNode>): Array<TreeNode> {
var result = Array<TreeNode>();
for (let node of nodes) {
var n = new TreeNode(node.id, node.name, this.RecursiveMapNodes(node.nodes));
result.push(n);
}
return result;
}
}
TreeNode.ts (i renamed Node to treeNode)
export class TreeNode {
id: number;
name: string;
nodes: Array<TreeNode>;
checked: boolean;
expanded: boolean;
constructor(id: number, name: string, nodes: Array<TreeNode>) {
this.id = id;
this.name = name;
this.nodes = nodes;
this.checked = false;
this.expanded = false;
}
toggle() {
this.expanded = !this.expanded;
}
check() {
let newState = !this.checked;
this.checked = newState;
this.checkRecursive(newState);
}
checkRecursive(state) {
this.nodes.forEach(d => {
d.checked = state;
d.checkRecursive(state);
})
}
}
functionService.ts
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { TreeNode } from '../classes/TreeNode';
import { Observable } from 'rxjs';
@Injectable()
export class FunctionService {
constructor(private http: HttpClient, @Inject('BASE_URL') private baseUrl: string) { }
getNodeData(): Observable<Array<TreeNode>> {
return this.http.get<TreeNode[]>(this.baseUrl + 'api/FunctionNodes/GetNodes');
}
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { AppComponent } from './components/app.component';
import { NavMenuComponent } from './components/nav-menu/nav-menu.component';
import { HomeComponent } from './components/home/home.component';
import { CounterComponent } from './components/counter/counter.component';
import { FetchDataComponent } from './components/fetch-data/fetch-data.component';
import { MainWindowComponent } from './components/main-window/main-window.component';
import { TreeViewComponent } from './components/tree-view/tree-view.component';
import { FunctionService } from './services/functionService';
@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
CounterComponent,
FetchDataComponent,
MainWindowComponent,
TreeViewComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
RouterModule.forRoot([
{ path: '', component: MainWindowComponent, pathMatch: 'full' },
{ path: 'counter', component: CounterComponent },
{ path: 'fetch-data', component: FetchDataComponent },
])
],
providers: [FunctionService],
bootstrap: [AppComponent]
})
export class AppModule { }
来源:https://stackoverflow.com/questions/56478434/tree-view-constancy-being-nested-after-every-refresh