【问题标题】:How to create dropdown list and bind with data coming from server如何创建下拉列表并与来自服务器的数据绑定
【发布时间】:2020-06-04 16:47:48
【问题描述】:

我对 Angular 很陌生。我有一些关于 Angular 的工作。

我需要通过调用 Rest Api 为来自服务器的 Json 数据绑定嵌套下拉列表。

数据有一个属性Level,指定组的层次结构中的级别。家长 将有立即的Child=1Grandchild=2 等等。 ChildGrandchild 有 Group 字段,它显示了其中的父菜单,子菜单将在那里。

我已经尝试过开发它,但我在html 文件和单独的CSS 文件中找到了bootstrap 的所有示例,这对我来说很复杂。

我想使用TypeScript 动态执行此操作。我怎样才能开始工作。

【问题讨论】:

  • 首先,数据格式是XML而不是JSON。您还可以添加您尝试过的任何内容吗?也许更详细地说是您采用的方法。
  • @vatz88 - 是的,这是刚刚从邮递员那里粘贴的 xml。我尝试了具有静态嵌套列表的html 代码。我将尝试对其进行编辑并发布Json 数据。你不会喜欢我尝试过的:)
  • 你不必担心到目前为止你已经编码了什么。方法是 - 对 ts 文件中的数据进行硬编码,在 html 中根据您必须呈现下拉列表的数据进行绑定。一旦逻辑正确,就可以动态获取数据,然后让 angular 与绑定发挥作用。
  • @vatz88 - 我的静态代码在html 文件中。我有想法开始它。你可以帮帮我。
  • @ArvindChourasiya 有很多 LgLevel 1 的孩子,如何识别 LgLevel 2 的哪个孙子属于哪个孩子?

标签: html angular typescript


【解决方案1】:

这是一个示例,您需要根据 json 数据中的嵌套级别数据对其进行编码。现在您可以使用 model 数据在 DOM 中循环 formatted json 数据。 我希望这将帮助您创建一个多级下拉菜单

groupBy(xs, key) {
   return xs.reduce(function (rv, x) {
     (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
   }, {});
 }

var model;

getData() {
 var   sampleData = {
  "ArrayOfLocationGroup": {
    "LocationGroup": [
      ...
      ...//Server response data
      ],
    "_xmlns:xsd": "http://www.w3.org/2001/XMLSchema"
  }
 }    

var list = this.sampleData["ArrayOfLocationGroup"]["LocationGroup"];
var formattedList = [];

list.forEach(element => {

  var obj = {  //Make sure your server response data to like this structure
    "Id": element.Id,
    "Name": element.Name,
    "GroupId": element.GroupId.__text,
    "ParentLocationGroup": element.ParentLocationGroup.__text,
    "LgLevel": element.LgLevel.__text,
    "Child" : []
  }
  formattedList.push(obj);
});

var groupDataList = this.groupBy(formattedList, "LgLevel");

var parents = groupDataList[0];
var child = groupDataList[1];
var childOfChild = groupDataList[2];

child.forEach(c => {
  c.Child = childOfChild.filter(x => x.ParentLocationGroup == c.Id);
})

parents.forEach(p => {
  p.Child = child.filter(x => x.ParentLocationGroup == p.Id);
})

this.model = parents;
}

HTML 文件

    <ul class="nav site-nav">
     <li class=flyout>
      <a href=#>Dropdown</a>
      <!-- Flyout -->
      <ul class="flyout-content nav stacked">
        <li *ngFor="let parent of model" [class.flyout-alt]="parent.Child.length > 0"><a href=#>{{parent.Name}}</a>
          <ul *ngIf="parent.Child.length > 0" class="flyout-content nav stacked">
            <li *ngFor="let c of parent.Child" [class.flyout-alt]="c.Child.length > 0"><a href=#>{{c.Name}}</a>
              <ul *ngIf="c.Child.length > 0" class="flyout-content nav stacked">
                <li *ngFor="let cc of c.Child" [class.flyout-alt]="cc.Child.length > 0"><a href=#>{{cc.Name}}</a></li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>

根据您的服务器响应数据组织模型数据。 响应 json 格式已更改__text 改为 #text

 var obj = {
    "Id": element.Id,
    "Name": element.Name && element.Name.#text ? element.Name.#text : element.Name,
    "GroupId": element.GroupId && element.GroupId.#text ? element.GroupId.#text : element.GroupId,
    "ParentLocationGroup": element.ParentLocationGroup && element.ParentLocationGroup.#text ? element.ParentLocationGroup.#text : element.ParentLocationGroup,
    "LgLevel": element.LgLevel && element.LgLevel.#text ? element.LgLevel.#text : element.LgLevel,
    "Child" : []
  }

【讨论】:

  • 能否请您也发布 .html 文件。
  • @ArvindChourasiya 您需要选择标签下拉菜单还是只需要下拉菜单 (link)?
  • 我需要像上面链接中显示的 dolor 选项一样。
  • 我对你的代码有点困惑。您没有在任何地方使用getData。您能否检查您的代码一并添加开头和结尾。
  • getData 只是一个方法,你可以在你想使用的地方调用它。在该方法中,我正在格式化 json 数据以显示在 html 中。这只是一个示例代码。
【解决方案2】:

您似乎已经有了另一个满足您要求的答案。但是这个解决方案花了我一些时间才想出来。所以还是决定发布它。

下面的代码sn-p用于构造父子层次数据的树状结构:

  processData(data) {
    let locationData = data.ArrayOfLocationGroup.LocationGroup;
    let level0 = [];
    let tempMap = {};
    for (let i = 0; i < locationData.length; i++) {
      let currItem = this.getDataObject(locationData[i]);
      if (tempMap[currItem.id] == undefined) {
        tempMap[currItem.id] = currItem;
        if (tempMap[currItem.parentLocationGroup] == undefined) {
          tempMap[currItem.parentLocationGroup] = { children: [] };
        }
        tempMap[currItem.parentLocationGroup].children.push(currItem);
      } else {
        if (tempMap[currItem.id]) {
          currItem.children = tempMap[currItem.id].children;
        }
        tempMap[currItem.id] = currItem;
        if (tempMap[currItem.parentLocationGroup] == undefined) {
          tempMap[currItem.parentLocationGroup] = { children: [] };
        }
        tempMap[currItem.parentLocationGroup].children.push(currItem);
      }
      if (currItem.lgLevel == "0") {
        if (level0.indexOf(currItem) == -1) {
          level0.push(currItem);
        }
      }
    }
    this.levelData = level0;
  }

聚合数据作为输入传递给dropdown 组件,该组件将其呈现为多级下拉菜单。

这个解决方案应该适用于任何级别的孩子。可以修改dropdown 组件,以根据您的要求更改数据的呈现方式。

我从这里选择了htmlcss 作为多级下拉菜单:
https://phppot.com/css/multilevel-dropdown-menu-with-pure-css/
在此答案外部单击时关闭菜单下拉菜单的代码:
https://stackoverflow.com/a/59234391/9262488

希望你觉得这很有用。

【讨论】:

  • 我已经检查了你的代码。它正在工作。感谢您发布答案。你能告诉我你是怎么得到我的数据的吗?用于 http 请求(mocky.io)。
  • Mocky 是一个用于创建 mock rest api 的在线工具。我获取了您发布的数据并使用它创建了一个使用 mocky 的 rest api。
【解决方案3】:

为什么不创建一个树组件并递归地将输入绑定到它?

建议的解决方案是

  • 与深度无关 - 它适用于数据树中的任何级别(即使它会临时更改)
  • 非常高效 - 它将您的数据汇总到 O(n)

首先设计数据模型 - 它必须是树节点结构:

export interface GroupId { /* appropriate members... */ }

export interface ParentLocationGroup { /* other members... */ __text: string; }

export interface LgLevel { /* other members... */ __text: string; }

export interface DataModel {
  Id: string,
  Name: string,
  GroupId: GroupId,
  ParentLocationGroup: ParentLocationGroup,
  LgLevel: LgLevel,
  children: DataModel[]
}

然后将您的数据聚合到顶级组件中(或者甚至更好 - 在您的数据服务中;您应该能够很容易地抽象出来):

// dropdown.component.ts

@Component({
  selector: 'app-dropdown',
  template: `
    <ul class="nav site-nav">
      <li class=flyout>
        <a href=#>Dropdown</a>
        <app-dynamic-flyout [data]="data"></app-dynamic-flyout>
      </li>
    </ul>
  `
})
export class DropdownComponent {

  data: DataModel[];

  constructor(dataService: YourDataService){

    let data;
    dataService.getYourData()
      .pipe(map(d => d.ArrayOfLocationGroup.LocationGroup)))
      // Make sure every item has the `children` array property initialized
      // since it does not come with your data
      .subscribe(d => data = d.map(i => ({...i, children: []})));

    // Create a lookup map for building the data tree
    let idMap = data.reduce((acc, curr) => ({...acc, [curr.Id]: curr}), {});
    this.data = data.reduce(
      (acc, curr) => curr.LgLevel.__text == 0 || !curr.ParentLocationGroup
        // Either the level is 0 or there is no parent group
        // (the logic is unclear here - even some of your lvl 0 nodes have a `ParentGroup`)
        // -> we assume this is a top-level node and put it to the accumulator
        ? [...acc, curr]
        // Otherwise push this node to an appropriate `children` array
        // and return the current accumulator
        : idMap[curr.ParentLocationGroup.__text].children.push(curr) && acc, 
      []
    );
  }
}

并创建循环动态弹出组件:

// dynamic-flyout.component.ts

@Component({
  selector: 'app-dynamic-flyout',
  template: `
    <ul *ngIf="!!data.length" class="flyout-content nav stacked">
      <li *ngFor="let item of data" [class.flyout-alt]="!!item.children.length">
        <a href=#>{{item.Name}}</a>
        <app-dynamic-flyout [data]="item.children"></app-dynamic-flyout>
      </li>
    </ul>
  `
})
export class DynamicFlyoutComponent {
  @Input() data: DataModel[];
}

该解决方案未经测试,但它会为您指明正确的方向...

希望这会有所帮助:-)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-14
    • 2021-09-25
    • 1970-01-01
    • 2011-04-12
    相关资源
    最近更新 更多