Постоянство представления дерева, вложенное после каждого обновления - PullRequest
0 голосов
/ 06 июня 2019

Я новичок в Angular (7) с фоном c #. Я использую asp.net core 2.2 и использую шаблон по умолчанию, который поставляется с новым угловым проектом (home, counter, fetch-data). Древовидное представление ограничено и исходит от контроллера.

Я ожидаю

> Europe
>>  England
>>>   Manchester
>>>   London
>>>>    London City
>>>>    Stratford
>>  Germany

однако, я получаю, каждый раз, когда я расширяюсь

>   Europe
>>    Europe
>>>      Europe
>>>>        Europe

и т. Д.

мой код (домашняя страница, которая отображается сразу после загрузки МКС)

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

Ответы [ 2 ]

1 голос
/ 07 июня 2019

Здравствуйте. Я создал небольшое демо, которое вы можете использовать в качестве справочного материала. Для решения проблемы, которую вы решаете, вы можете найти ее здесь по адресу CodeSandbox .

Поскольку я не очень хорошо разбираюсь в части C #, я создал фиктивный бэкэнд-сервис, который должен действовать как ваш бэкэнд.

По поводу вопроса, почему это не работает, как вы уже упоминали в комментариях, каждый раз, когда вы инициализируете (переходите на уровень) свой tree-view.component.ts внутри его конструктора, вы выбираете данные, что приводит к всегда отображается «Европа» как результат.

При создании рекурсивных элементов (деревьев и т. Д.) Вы всегда должны предоставлять рекурсивному компоненту (в вашем случае 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
  })
  }
}
0 голосов
/ 07 июня 2019

Я публикую код вопроса и то, как он изменился с первого поста (FunctionNodesController.cs остается таким же, как и в оригинальном посте), я не знаю лучших практик, когда дело доходит до углового, так что если вы видите что-топлохо, пожалуйста, оставьте комментарий.(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 (я переименовал узел в 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 { }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...