C# 每次刷新后嵌套的树视图恒定性

C# 每次刷新后嵌套的树视图恒定性,c#,angular,asp.net-core,treeview,C#,Angular,Asp.net Core,Treeview,我是来自c#背景的新手。我正在使用asp.net core 2.2,并使用angular新angular项目(主页、计数器、获取数据)附带的默认模板。树视图已绑定并来自控制器 我期待着 > Europe >> England >>> Manchester >>> London >>>> London City >>>> Stratford >> Germany

我是来自c#背景的新手。我正在使用asp.net core 2.2,并使用angular新angular项目(主页、计数器、获取数据)附带的默认模板。树视图已绑定并来自控制器

我期待着

> Europe
>>  England
>>>   Manchester
>>>   London
>>>>    London City
>>>>    Stratford
>>  Germany
然而,每次我扩展时,我都会

>   Europe
>>    Europe
>>>      Europe
>>>>        Europe
等等

我的代码(加载ISS express后立即显示的主页)


您好,我已经创建了一个小演示,您可以作为参考,对于您正在解决的问题,您可以在这里找到它

由于我对C#part不是很了解,所以我创建了一个模拟后端服务,它应该充当您的后端

关于这个问题,为什么它不起作用,正如您在评论中已经提到的,每次您在初始化
树状视图.component.ts
时(进入一个级别),在它的构造函数中,您都在获取数据,这会导致始终显示“Europe”作为结果

创建递归元素(树等)时,必须始终向递归组件(在您的示例中是
树视图.component.ts
)提供树的下一层

例如,第一个“欧洲”=>['England'=>['Manchester'、'London'=>['London city'、'Stratford]]、'Germany'],其中每个
=>
正在构建新的
树视图.component.ts

//模板
当前节点={{node?.name}
展开节点
//组成部分
@组成部分({
选择器:“应用程序树”,
templateUrl:“./tree.component.html”
})
导出类TreeComponent实现OnInit{
@输入(“nodesForDisplay”)节点;
@输入(“级别”)级别=0;
开=假;

}
我正在发布问题代码,以及它与第一篇文章(FunctionNodesController.cs与原始文章保持一致)的变化,我不知道在角度方面的最佳做法,因此如果您看到不好的地方,请留下评论。(在这种情况下,FunctionNodesController可以重命名为RegionNodesController,FunctionService到RegionService的重命名方式与此类似)

树状视图.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);
    })
  }

}
 [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>();
            }
        }
    }
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 { }
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() {

  }
}
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;
  }

}
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');
  }
}
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 { }

你能给我们看一下加载在
树状视图.component.html中
节点
变量内部的BE结果吗?我不知道是什么原因每次我点击树状视图展开时,我在打印节点数组时得到以下结果:欧洲、英格兰、曼彻斯特、伦敦、伦敦城、斯特拉特福德,GermanyBE代表后端(抱歉,这是我的错),我指的是类似节点的结构中的内容,例如:[{name:node1,children:[{name:node_1-child},children:[]}],如果可能,您可以创建一个模拟后端响应的stackblitz。我假设问题在于您从巡演后端收到的响应结构中。好的,我一直在四处查看,从外观上看,我的TreeViewComponent.ts可能是一个问题。只要点击树进行扩展,它就会再次从控制器获取数据(从rootnode开始,即欧洲),因为treeviewComponent正在重新实例化,@Input的工作方式是将结果传递到下一个实例(从我在线阅读的内容),因此,每当节点发生变化(单击)时,我总是会重新获取数据。我想知道解决这个问题的最佳方法是什么。同时我会看看后端谢谢你,你对组件工作原理的解释帮助我理解了递归。我离开tree-view.ts时只使用了@Inputs,在主窗口中我使用了标记并传递了数据
<app-tree-view [nodes]="nodes"> </app-tree-view>
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;
  }

}
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);
    })
  }
}
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');
  }
}
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 { }