每次刷新后嵌套的树视图稳定性
Tree view constancy being nested after every refresh
我是 Angular (7) 的新手,具有 C# 背景。我正在使用 asp.net 核心 2.2 并使用 angular 新 angular 项目(home、counter、fetch-data)附带的默认模板。树视图已绑定并来自控制器。
我期待
> Europe
>> England
>>> Manchester
>>> London
>>>> London City
>>>> Stratford
>> Germany
然而,每次我展开时我都会得到
> Europe
>> Europe
>>> Europe
>>>> Europe
等等
我的代码(加载 ISS 快递后立即显示的主页)
<p>
Testing Componenets
<app-tree-view> </app-tree-view>
树-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>
树-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 { }
您好,我创建了一个小演示,您可以作为参考,对于您正在解决的问题,您可以在 CodeSandbox 找到它。
由于我对 C# 部分不是很了解,我创建了一个模拟 back-end 服务,它应该充当您的 back-end。
关于这个问题,为什么它不起作用,正如您在评论中已经提到的,每次您在构造函数中初始化(进入一个级别)您的 tree-view.component.ts
时,您都在获取数据,结果总是显示 'Europe' 作为结果。
创建递归元素(树等)时,您必须始终向递归组件(在您的情况下 tree-view.component.ts
)提供树的下一层。
例如首先 'Europe' => ['England' => ['Manchester' , 'London' => ['London city', 'Stratford] ] , 'Germany'] ,其中每个 =>
正在构建新的 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;
}
因此,为了摆脱这种总是获取第一层的僵局,您可以尝试创建一个包装器父组件,它处理获取并将数据传递给递归组件。
另一个技巧是从组件中删除 http 调用并将其放置在专用服务中,您将在需要的地方注入组件内部(如示例中所做的那样)
// 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
})
}
}
我正在 post 问题的代码以及它如何从第一个 post 改变(FunctionNodesController.cs 与原始 post 保持相同),我不知道关于 angular 的最佳做法,所以如果您看到不好的地方,请发表评论。 (对于这种情况,FunctionNodesController 可以重命名为 RegionNodesController,同样对于 FunctionService 可以重命名为 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(我将Node重命名为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 { }
我是 Angular (7) 的新手,具有 C# 背景。我正在使用 asp.net 核心 2.2 并使用 angular 新 angular 项目(home、counter、fetch-data)附带的默认模板。树视图已绑定并来自控制器。
我期待
> Europe
>> England
>>> Manchester
>>> London
>>>> London City
>>>> Stratford
>> Germany
然而,每次我展开时我都会得到
> Europe
>> Europe
>>> Europe
>>>> Europe
等等
我的代码(加载 ISS 快递后立即显示的主页)
<p>
Testing Componenets
<app-tree-view> </app-tree-view>
树-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>
树-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 { }
您好,我创建了一个小演示,您可以作为参考,对于您正在解决的问题,您可以在 CodeSandbox 找到它。
由于我对 C# 部分不是很了解,我创建了一个模拟 back-end 服务,它应该充当您的 back-end。
关于这个问题,为什么它不起作用,正如您在评论中已经提到的,每次您在构造函数中初始化(进入一个级别)您的 tree-view.component.ts
时,您都在获取数据,结果总是显示 'Europe' 作为结果。
创建递归元素(树等)时,您必须始终向递归组件(在您的情况下 tree-view.component.ts
)提供树的下一层。
例如首先 'Europe' => ['England' => ['Manchester' , 'London' => ['London city', 'Stratford] ] , 'Germany'] ,其中每个 =>
正在构建新的 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;
}
因此,为了摆脱这种总是获取第一层的僵局,您可以尝试创建一个包装器父组件,它处理获取并将数据传递给递归组件。
另一个技巧是从组件中删除 http 调用并将其放置在专用服务中,您将在需要的地方注入组件内部(如示例中所做的那样)
// 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
})
}
}
我正在 post 问题的代码以及它如何从第一个 post 改变(FunctionNodesController.cs 与原始 post 保持相同),我不知道关于 angular 的最佳做法,所以如果您看到不好的地方,请发表评论。 (对于这种情况,FunctionNodesController 可以重命名为 RegionNodesController,同样对于 FunctionService 可以重命名为 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(我将Node重命名为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 { }