遍历 Angular 中的对象

Iterate over object in Angular

我正在尝试在 Angular 2 Alpha 28 中做一些事情,但我遇到了字典和 ngFor.

的问题

我在 TypeScript 中有一个如下所示的界面:

interface Dictionary {
    [index: string]: string
}

在 JavaScript 中,这将转换为一个对象,其数据可能如下所示:

myDict={'key1':'value1','key2':'value2'}

我想遍历这个并试过这个:

<div *ngFor="(#key, #value) of myDict">{{key}}:{{value}}</div>

但无济于事,下面的 none 也有效:

<div *ngFor="#value of myDict">{{value}}</div>
<div *ngFor="#value of myDict #key=index">{{key}}:{{value}}</div>

在所有情况下,我都会收到类似 Unexpected tokenCannot find 'iterableDiff' pipe supporting object

的错误

我在这里错过了什么?这不可能了吗? (第一个语法在 Angular 1.x 中有效)还是迭代对象的语法不同?

In JavaScript this will translate to an object that with data might look like this

TypeScript 中的接口是开发时构造(纯粹用于工具...0 运行时影响)。您应该编写与 JavaScript 相同的 TypeScript。

字典是对象,不是数组。我相信 ng-repeat 需要一个数组 Angular 2.

最简单的解决方案是创建一个 pipe/filter 将对象动态转换为数组。也就是说,您可能想像@basarat 所说的那样使用数组。

看来他们不想支持 ng1 的语法。

根据 Miško Hevery (reference) 的说法:

Maps have no orders in keys and hence they iteration is unpredictable. This was supported in ng1, but we think it was a mistake and will not be supported in NG2

The plan is to have a mapToIterable pipe

<div *ngFor"var item of map | mapToIterable">

因此,为了遍历您的对象,您需要使用 "pipe"。 目前还没有 pipe 实现那样做。

作为解决方法,这里有一个迭代键的小示例:

分量:

import {Component} from 'angular2/core';

@Component({
  selector: 'component',
  templateUrl: `
       <ul>
       <li *ngFor="#key of keys();">{{key}}:{{myDict[key]}}</li>
       </ul>
  `
})
export class Home {
  myDict : Dictionary;
  constructor() {
    this.myDict = {'key1':'value1','key2':'value2'};
  }

  keys() : Array<string> {
    return Object.keys(this.myDict);
  }
}

interface Dictionary {
    [ index: string ]: string
}

尝试使用这个管道

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'values',  pure: false })
export class ValuesPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value).map(key => value[key]);
  }
}

<div *ngFor="#value of object | values"> </div>

Angular 6.1.0+ 答案

像这样使用内置 keyvalue-pipe

<div *ngFor="let item of myObject | keyvalue">
    Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>

或者像这样:

<div *ngFor="let item of myObject | keyvalue:mySortingFunction">
    Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>

其中 mySortingFunction 在您的 .ts 文件中,例如:

mySortingFunction = (a, b) => {
  return a.key > b.key ? -1 : 1;
}

Stackblitz:https://stackblitz.com/edit/angular-iterate-key-value

您不需要在任何模块中注册它,因为 Angular 管道在任何模板中都是开箱即用的。

它也适用于 Javascript-Maps

除了@obscur 的回答之外,这里还有一个示例,说明如何从@View 访问keyvalue

管道:

@Pipe({
   name: 'keyValueFilter'
})

export class keyValueFilterPipe {
    transform(value: any, args: any[] = null): any {

        return Object.keys(value).map(function(key) {
            let pair = {};
            let k = 'key';
            let v = 'value'


            pair[k] = key;
            pair[v] = value[key];

            return pair;
        });
    }

}

查看:

<li *ngFor="let u of myObject | 
keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>

所以如果对象看起来像:

myObject = {
    Daario: Naharis,
    Victarion: Greyjoy,
    Quentyn: Ball
}

生成的结果将是:

名字:达里奥
姓氏:纳哈里斯

名字:Victarion
姓氏:葛雷乔伊

名字:Quentyn
姓氏:鲍尔

我有一个类似的问题,为对象和地图构建了一些东西。

import { Pipe } from 'angular2/core.js';

/**
 * Map to Iteratble Pipe
 * 
 * It accepts Objects and [Maps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
 * 
 * Example:
 * 
 *  <div *ngFor="#keyValuePair of someObject | mapToIterable">
 *    key {{keyValuePair.key}} and value {{keyValuePair.value}}
 *  </div>
 * 
 */
@Pipe({ name: 'mapToIterable' })
export class MapToIterable {
  transform(value) {
    let result = [];
    
    if(value.entries) {
      for (var [key, value] of value.entries()) {
        result.push({ key, value });
      }
    } else {
      for(let key in value) {
        result.push({ key, value: value[key] });
      }
    }

    return result;
  }
}

增加 SimonHawesome 的 . I've made an succinct version which utilizes some of the new typescript features. I realize that SimonHawesome's version is intentionally verbose as to explain the underlying details. I've also added an early-out check so that the pipe works for falsy 值。例如,如果地图是 null.

请注意,使用迭代器转换(如此处所做的)可以更有效,因为我们不需要为临时数组分配内存(如其他一些答案中所做的那样)。

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
    name: 'mapToIterable'
})
export class MapToIterable implements PipeTransform {
    transform(map: { [key: string]: any }, ...parameters: any[]) {
        if (!map)
            return undefined;
        return Object.keys(map)
            .map((key) => ({ 'key': key, 'value': map[key] }));
    }
}

如果你有 es6-shim 或你的 tsconfig.json 目标 es6,你可以使用 ES6 Map 来实现它。

var myDict = new Map();
myDict.set('key1','value1');
myDict.set('key2','value2');

<div *ngFor="let keyVal of myDict.entries()">
    key:{{keyVal[0]}}, val:{{keyVal[1]}}
</div>

如果有人想知道如何使用多维对象,这里是解决方案。

假设我们在 service

中有以下对象
getChallenges() {
    var objects = {};
    objects['0'] = { 
        title: 'Angular2', 
        description : "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
    };

    objects['1'] = { 
        title: 'AngularJS', 
        description : "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
    };

    objects['2'] = { 
        title: 'Bootstrap',
        description : "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
    };
    return objects;
}

在组件中添加以下功能

challenges;

constructor(testService : TestService){
    this.challenges = testService.getChallenges();
}
keys() : Array<string> {
    return Object.keys(this.challenges);
}

终于在视图中执行以下操作

<div *ngFor="#key of keys();">
    <h4 class="heading">{{challenges[key].title}}</h4>
    <p class="description">{{challenges[key].description}}</p>
</div>

下面是支持多重转换(键值、键、值)的上述某些答案的变体:

import { Pipe, PipeTransform } from '@angular/core';

type Args = 'keyval'|'key'|'value';

@Pipe({
  name: 'mapToIterable',
  pure: false
})
export class MapToIterablePipe implements PipeTransform {
  transform(obj: {}, arg: Args = 'keyval') {
    return arg === 'keyval' ?
        Object.keys(obj).map(key => ({key: key, value: obj[key]})) :
      arg === 'key' ?
        Object.keys(obj) :
      arg === 'value' ?
        Object.keys(obj).map(key => obj[key]) :
      null;
  }
}

用法

map = {
    'a': 'aee',
    'b': 'bee',
    'c': 'see'
}

<div *ngFor="let o of map | mapToIterable">{{o.key}}: {{o.value}}</div>
  <div>a: aee</div>
  <div>b: bee</div>
  <div>c: see</div>

<div *ngFor="let o of map | mapToIterable:'keyval'">{{o.key}}: {{o.value}}</div>
  <div>a: aee</div>
  <div>b: bee</div>
  <div>c: see</div>

<div *ngFor="let k of map | mapToIterable:'key'">{{k}}</div>
  <div>a</div>
  <div>b</div>
  <div>c</div>

<div *ngFor="let v of map | mapToIterable:'value'">{{v}}</div>
  <div>aee</div>
  <div>bee</div>
  <div>see</div>

Angular 2.x && Angular 4.x 不支持开箱即用

您可以使用这两个管道通过 keyvalue.

进行迭代

键管道:

import {Pipe, PipeTransform} from '@angular/core'

@Pipe({
  name: 'keys',
  pure: false
})
export class KeysPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value)
  }
}

值管道:

import {Pipe, PipeTransform} from '@angular/core'

@Pipe({
  name: 'values',
  pure: false
})
export class ValuesPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value).map(key => value[key])
  }
}

使用方法:

let data = {key1: 'value1', key2: 'value2'}

<div *ngFor="let key of data | keys"></div>
<div *ngFor="let value of data | values"></div>

我一直在努力解析和使用 return 形成 JSON 查询/api 调用的数据。我不确定我到底哪里出错了,我觉得我已经盘旋了好几天的答案,追逐各种错误代码,例如:

"Cannot find 'iterableDiff' pipe supporting object"

"Generic TYpe Array requires one argument(s)"

JSON 解析错误,我相信其他人

我假设我的修复组合有误。

所以这里有一些陷阱和需要寻找的东西的总结。

首先检查您的 api 调用的结果,您的结果可能是对象、数组或对象数组的形式。

我不会过多地讨论它,只要说 OP 的原始不可迭代错误通常是由于您尝试迭代一个对象而不是数组引起的。

Heres some of my debugging results showing variables of both arrays and objects

因此,由于我们通常希望遍历 JSON 结果,因此我们需要确保它采用数组形式。我尝试了很多例子,也许知道我现在所知道的,其中一些实际上会起作用,但我采用的方法确实是实现一个管道,我使用的代码是由 t.888

发布的
   transform(obj: {[key: string]: any}, arg: string) {
if (!obj)
        return undefined;

return arg === 'keyval' ?
    Object.keys(obj).map((key) => ({ 'key': key, 'value': obj[key] })) :
  arg === 'key' ?
    Object.keys(obj) :
  arg === 'value' ?
    Object.keys(obj).map(key => obj[key]) :
  null;

老实说,我认为让我困扰的事情之一是缺乏错误处理,通过添加 'return undefined' 调用我相信我们现在允许将非预期数据发送到管道,这显然发生在我的情况下。

如果你不想处理管道的参数(并且看起来我认为在大多数情况下没有必要)你可以return以下

       if (!obj)
          return undefined;
       return Object.keys(obj);

关于创建管道和使用该管道的页面或组件的一些注意事项

我是否收到有关未找到“name_of_my_pipe”的错误消息

使用 CLI 中的“ionic generate pipe”命令确保正确创建和引用管道 modules.ts。确保将以下内容添加到 mypage.module.ts 页面。

import { PipesModule } from ‘…/…/pipes/pipes.module’;

(如果您也有自己的 custom_module,不确定这是否会改变,您可能还需要将其添加到 custommodule.module.ts)

如果您使用 'ionic generate page' 命令制作页面,但决定将该页面用作主页,请记住从 app.module.ts 中删除页面引用(这是我发布的另一个答案https://forum.ionicframework.com/t/solved-pipe-not-found-in-custom-component/95179/13?u=dreaser

在我寻找答案的过程中,有多种方法可以显示 html 文件中的数据,但我不太了解这些差异,无法解释这些差异。您可能会发现在某些情况下使用一个比另一个更好。

            <ion-item *ngFor="let myPost of posts">
                  <img src="https://somwhereOnTheInternet/{{myPost.ImageUrl}}"/>
                  <img src="https://somwhereOnTheInternet/{{posts[myPost].ImageUrl}}"/>
                  <img [src]="'https://somwhereOnTheInternet/' + myPost.ImageUrl" />
            </ion-item>

然而,允许我同时显示值和键的方法如下:

    <ion-list>  
      <ion-item *ngFor="let myPost of posts  | name_of_pip:'optional_Str_Varible'">

        <h2>Key Value = {{posts[myPost]}} 

        <h2>Key Name = {{myPost}} </h2>

      </ion-item>
   </ion-list>  

为了使 API 调用看起来您需要将 HttpModule 导入 app.module.ts

import { HttpModule } from '@angular/http';
 .
 .  
 imports: [
BrowserModule,
HttpModule,

并且您需要在调用的页面中使用 Http

import {Http} from '@angular/http';

在进行 API 调用时,您似乎能够通过 2 种不同的方式获取子数据(数组中的对象或数组),这两种方式似乎都有效

通话中

this.http.get('https://SomeWebsiteWithAPI').map(res => res.json().anyChildren.OrSubChildren).subscribe(
        myData => {

或者当您将数据分配给局部变量时

posts: Array<String>;    
this.posts = myData['anyChildren'];

(不确定该变量是否需要是数组字符串,但这就是我现在拥有的。它可以用作更通用的变量)

最后请注意,没有必要使用内置的 JSON 库 然而,您可能会发现这 2 个调用对于从对象到字符串的转换非常方便,反之亦然

        var stringifiedData = JSON.stringify(this.movies);                  
        console.log("**mResults in Stringify");
        console.log(stringifiedData);

        var mResults = JSON.parse(<string>stringifiedData);
        console.log("**mResults in a JSON");
        console.log(mResults);

我希望这些信息汇编能帮助到一些人。

更新:Angular 现在提供通过 keyvalue 遍历 json 对象的管道:

<div *ngFor="let item of myDict | keyvalue">
  {{item.key}}:{{item.value}}
</div>

WORKING DEMO , and for more detail Read


以前(对于旧版本):到目前为止,我找到的最佳/最短答案是(没有任何管道过滤器或组件端的自定义功能)

Component side :

objectKeys = Object.keys;

Template side :

<div *ngFor='let key of objectKeys(jsonObj)'>
   Key: {{key}}

    <div *ngFor='let obj of jsonObj[key]'>
        {{ obj.title }}
        {{ obj.desc }}
    </div>

</div>

WORKING DEMO

定义MapValuesPipe并实现PipeTransform:

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({name: 'mapValuesPipe'})
export class MapValuesPipe implements PipeTransform {
    transform(value: any, args?: any[]): Object[] {
        let mArray: 
        value.forEach((key, val) => {
            mArray.push({
                mKey: key,
                mValue: val
            });
        });

        return mArray;
    }
}

在你的管道模块中添加你的管道。如果您需要使用 :

,这一点很重要
@NgModule({
  imports: [
    CommonModule
  ],
  exports: [
    ...
    MapValuesPipe
  ],
  declarations: [..., MapValuesPipe, ...]
})
export class PipesAggrModule {}

然后只需将 html 中的管道与 *ngFor:

一起使用

<tr *ngFor="let attribute of mMap | mapValuesPipe">

记住,您需要在要使用管道的组件中声明 PipesModule:

@NgModule({
  imports: [
    CommonModule,
    PipesAggrModule
  ],
...
}
export class MyModule {}
//Get solution for ng-repeat    
//Add variable and assign with Object.key

    export class TestComponent implements OnInit{
      objectKeys = Object.keys;
      obj: object = {
        "test": "value"
        "test1": "value1"
        }
    }
    //HTML
      <div *ngFor="let key of objectKeys(obj)">
        <div>
          <div class="content">{{key}}</div>
          <div class="content">{{obj[key]}}</div>
        </div>

所以我打算实现自己的辅助函数 objLength(obj),returns 只是 Object(obj).keys.length。但是当我将它添加到我的模板 *ngIf 函数时,我的 IDE 建议使用 objectKeys()。我试过了,它奏效了。按照它的声明,它似乎由 lib.es5.d.ts 提供,所以你去吧!

我是这样实现的(我有一个自定义对象,它使用服务器端生成的密钥作为我上传文件的索引):

        <div *ngIf="fileList !== undefined && objectKeys(fileList).length > 0">
          <h6>Attached Files</h6>
          <table cellpadding="0" cellspacing="0">
            <tr *ngFor="let file of fileList | keyvalue">
              <td><a href="#">{{file.value['fileName']}}</a></td>
              <td class="actions">
                <a title="Delete File" (click)="deleteAFile(file.key);">
                </a>
              </td>
            </tr>
          </table>
        </div>

还有另一种循环对象的方法,使用结构指令:

我更喜欢这种方法,因为它“感觉”最像普通的 ngFor 循环。 :-)

(在这种情况下,例如我添加了 Angular 的上下文变量 let i = index | even | odd | first | last | count)在我的循环中)。

@Directive({
  selector: '[ngForObj]'
})
export class NgForObjDirective implements OnChanges {

  @Input() ngForObjOf: { [key: string]: any };

  constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) { }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.ngForObjOf && changes.ngForObjOf.currentValue) {
      // remove all views
      this.viewContainerRef.clear();

      // create a new view for each property
      const propertyNames = Object.keys(changes.ngForObjOf.currentValue);
      const count = propertyNames.length;

      propertyNames.forEach((key: string, index: number) => {
        const even = ((index % 2) === 0);
        const odd = !even;
        const first = (index === 0);
        const last = index === (count - 1);

        this.viewContainerRef.createEmbeddedView(this.templateRef, {
          $implicit: changes.ngForObjOf.currentValue[key],
          index,
          even,
          odd,
          count,
          first,
          last
        });
      });
    }
  }
}

在您的模板中的用法:

<ng-container *ngForObj="let item of myObject; let i = index"> ... </ng-container>

如果你想使用整数值循环,你可以使用这个指令:

@Directive({
   selector: '[ngForInt]'
})
export class NgForToDirective implements OnChanges {

  @Input() ngForIntTo: number;
 
  constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) {

  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.ngForIntTo && changes.ngForIntTo.currentValue) {
      // remove all views
      this.viewContainerRef.clear();

      let currentValue = parseInt(changes.ngForIntTo.currentValue);
      for (let index = 0; index < currentValue; index++) {
        this.viewContainerRef.createEmbeddedView(this.templateRef, {
          $implicit: index,
          index
        });
      }

    }

  }
}

模板中的用法(示例:从 0 循环到 14(= 15 次迭代):

<ng-container *ngForInt="let x to 15"> ... </ng-container>