如何在视图中仅循环一次对象(单个 *ngFor)?

Posted

技术标签:

【中文标题】如何在视图中仅循环一次对象(单个 *ngFor)?【英文标题】:How to loop through an object only once in the view (single *ngFor)? 【发布时间】:2021-06-10 22:09:54 【问题描述】:

我有这个对象,每个属性都有一个数组作为它的值。我想只循环一次这个对象,而不是使用 2 个 *ngFor 指令,因为它会导致我的应用程序运行缓慢。我也愿意用这个对象创建一个数组,但我仍然想为每个部分添加一个部分 id,如下例所示。谁能指出我正确的方向?提前非常感谢!这是我的代码。

LIVE DEMO

<div *ngFor="let group of dataObject | keyvalue">
    group.key
    <div *ngFor="let item of group.value; let i=index;">
        item.name
    </div>
</div>

注意:我的最终目标是将每个人包装到他们所属的部分中,并为每个部分添加一个 ID。 示例:

  <div id = "parentId"> 
     <div id = "section-a">
        list of all users that belong in section a
     </div>
     <div id = "section-b">
         list of all users that belong in section b
     </div>
     <div id = "section-c">
         list of all users that belong in section c
     </div>
 
    <div>
    ......
    ......
  </div>

【问题讨论】:

【参考方案1】:

使用*ngFor 无法做到这一点。您可以在组件中准备数据的平面版本,并提供一个 1-depth 数组以仅循环一次。

示例应用组件:

import  Component  from "@angular/core";

@Component(
  selector: "app-root",
  templateUrl: "./app.component.html"
)
export class AppComponent 
  
  dataObject = 
    A: [
       id: 1, name: "Mike", lastname: "asmith" ,
       id: 2, name: "Mary", lastname: "alters" ,
       id: 3, name: "Boris", lastname: "al" 
    ],
    B: [
       id: 1, name: "Mike", lastname: "bmith" ,
       id: 2, name: "Mary", lastname: "balters" ,
       id: 3, name: "Boris", lastname: "bl" 
    ],
    C: [
       id: 1, name: "Mike", lastname: "cmith" ,
       id: 2, name: "Mary", lastname: "calters" ,
       id: 3, name: "Boris", lastname: "dl" 
    ],
    D: [
       id: 1, name: "Mike", lastname: "dmith" ,
       id: 2, name: "Mary", lastname: "dalters" ,
       id: 3, name: "Boris", lastname: "dl" 
    ],
    E: [
       id: 1, name: "Mike", lastname: "emith" ,
       id: 2, name: "Mary", lastname: "ealters" ,
       id: 3, name: "Boris", lastname: "el" 
    ]
  ;

  flatDataObject = [];

  constructor() 
    // Loop through dataObject
    Object.keys(this.dataObject).forEach(k => 
      // Assign key as a `key` attribute to first object in array.
      this.dataObject[k][0].key = k;
      // Push the arrays into one
      this.flatDataObject.push(...this.dataObject[k]);
    );
  

示例 HTML:

<div *ngFor="let group of flatDataObject">
  <!-- Check if the object has `key` attribute -->
  <div *ngIf="group.key">
    <!-- If it has `key` attribute: -->
    group.key
  </div>
  <div>
    group.lastname
  </div>
</div>

这里是演示:https://stackblitz.com/edit/angular-6-template-sk8tkm?file=src/app/app.component.html

这是我能想到的最佳解决方案。它仍然总共有两个循环,但至少其中一个没有使用*ngFor

【讨论】:

如何为每个部分添加一个带有 id 的 div?例如,对于“A”部分及其用户,您如何将其包装在 id =“section-a”的部分中,对于“B”部分,如何将其包装在 id =“section-b”的 div 中,等等开吗? @progx 您无法使用我的解决方案为整个组分配 div id,因为它的设计目的是遍历数据集的扁平版本。但是您可以根据自己的解决方案设置 div id。示例: 我的解决方案?你的意思是回去使用 2 *ngFor 指令?我只想使用一个 *ngFor。 @progx 我回答了你的问题,而不是你的评论。当涉及到您的第二个问题时,您基本上是在创建一个 1 深度的 div 循环,例如:section-a-1, section-a-2, section-a-3, section-b-1 .... 你可以不要将它们分组并用组 ID 包装它们,因为没有父 div。如果您想这样做,请保留您的代码并使用 更改第一个 div。 【参考方案2】:

您有一个 DictionaryArrays(作为数据结构),因此对于必要的迭代,您有以下时间复杂度:

O(n) : n 代表字典长度 O(m) : m 代表Array类型)的长度

您执行迭代的总复杂度为O(mn)O(m*n) 任何地方。例如,下面的代码具有相同的时间复杂度:

Object.entries(this.dataObject).reduce((result, entry) => 
    const [key, value] = entry;
    value.forEach((v, i) => (result[`$key_$i`] = v.name));
    return result;
,);

如果你有一个限制,我认为你可以降低时间复杂度在语言方法和方便的技巧的帮助下,如下所示:

Object.values(this.dataObject).map(m => m).flat().map(m => m.name);

然后,将每个 3 项拆分:

<div *ngFor="let item of simplified2; let i = index" [id]="findId(i)">
  findId(i) item
</div>


findId(index: number) 
    return Math.floor(index / 3); //if you are sure about the length of values

我在stackblitz 中提供了上述所有内容。你可以了解更多关于Data Structures in javascript: Arrays, HashMaps, and Lists

【讨论】:

我想要与我相同的输出(标题 - 例如“A”及其子元素下方),然后我想为每个部分添加 div id,例如“section-a”对于 A 部分,对于部分“B”,“section-b”,依此类推。你知道怎么做吗? @progx 正如我之前所描述的,您最终将拥有与您自己的代码相同的时间复杂度来满足您的目的,即O(nm)性能问题 基于必要的迭代。想到的唯一方法是分页技术来控制性能并按需加载请求的部分 @progx 你想要超越计算机算法的东西!【参考方案3】:

您可以生成减少的新属性并使用此新属性

在你的组件中

dataObject = 
    A: [
        id: 1, name: 'Mike', lastname: 'asmith',
        id: 2, name: 'Mary', lastname: 'alters',
        id: 3, name: 'Boris', lastname: 'al'
    ],
    B: [
        id: 1, name: 'Lauren', lastname: 'bmith',
        id: 2, name: 'Travis', lastname: 'balters',
        id: 3, name: 'Barn', lastname: 'bl'
    ], 
    ...

dataObjectDisp = Object.entries(this.dataObject).reduce((prev, [key, value]) =>
  return [...prev, ...value.map((user, index) => ( 
    ...user, parent: key, index ))]
, [])

以上代码将结构简化为

[
  
    "id": 1,
    "name": "Mike",
    "lastname": "asmith",
    "parent": "A",
    "index": 0
  ,
  
    "id": 2,
    "name": "Mary",
    "lastname": "alters",
    "parent": "A",
    "index": 1
  ,
  ...
]

现在我们有一种方法可以确定新部分何时开始。它的索引为零

要显示 html,您可以执行以下操作

<h1>New</h1>
<div *ngFor="let item of dataObjectDisp; let i = index" [attr.data-article]='"section" + item.parent'>
  <ng-container *ngIf="item.index === 0">
     item.parent 
  </ng-container>
  <div>
    item.lastname
  </div>
</div>

注意属性[attr.data-article]='"section" + item.parent'。这可以用来识别不同的部分

【讨论】:

您知道您的解决方案的时间复杂度是多少吗? OP 存在性能问题。 @Owen Kelvin 你能提供像 stackblitz 这样的现场演示吗? 我想用 id 将所有部分包装在一个 div 中。例如对于 A 部分,我想添加一个 id = "sectionA" 并且它的所有子元素都应该在里面。您的解决方案为每个孩子(每个人)添加了一个“”sectionA。希望这是有道理的 stackblitz.com/edit/angular-6-template-sdebr2 @Owen Kelvin 我试图坚持使用单 *ngFor,而您的新解决方案使用双 *ngFor。但是,我非常感谢您的工作和努力,所以我给您额外的 100 点声望点。谢谢老哥!

以上是关于如何在视图中仅循环一次对象(单个 *ngFor)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 1 个单个 div 中生成 *ngFor 循环项,而不是每个项目周围都有一个 div 容器

如何在角度 2 中使用 ngFor 仅循环遍历数组到某些对象

结果集在while循环中仅迭代一次

如何将图像源绑定到 ngFor 循环内的可观察对象?

如何在Angular中使用ngFor循环对象属性

如何使用 *ngFor 循环我的数据对象?