【问题标题】:How can I use *ngFor to loop my data object?如何使用 *ngFor 循环我的数据对象?
【发布时间】:2025-12-07 11:25:01
【问题描述】:

这是我的“monthlyCalendar”对象:

{
    "monthName": "September 2019",
    "dateObjList": {
        "1": {
            "dayOfWeek": "0",
            "isPublicHoliday": false,
            .............
        },
        "2": {
            "dayOfWeek": "0",
            "isPublicHoliday": true,
            .............
        },
        .....................
    }
}   

这是我的组件.ts:

...........
ngOnInit() {
    this.monthlyCalendarService.getMonthlyCalendar(null, null).subscribe(
      (res: MonthlyCalendar) => {
        this.monthlyCalendar = res;
        this.monthName = this.monthlyCalendar.monthName;
      },
      (error: Error) => {
        alert('Something wrong when getting Monthly Calendar data.\n' + error.message);
      },
    );
  }
.........

这是我每月的CalendarService.ts:

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { MonthlyCalendar } from './monthly-calendar';
import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
@Injectable({
  providedIn: 'root'
})
export class MonthlyCalendarService {

  constructor(private http: HttpClient) { }

  getMonthlyCalendar(year: string, month: string): Observable<MonthlyCalendar> {
    const params = new HttpParams();

    params.set('year', year);
    params.set('month', month);

    return this.http.get('backend/getMonthlyCalendar.php', {params}).pipe(map((res: MonthlyCalendar) => res));
    /*
    return this.http.get('backend/getMonthlyCalendar.php', {params})
    .pipe(map((res: MonthlyCalendar) =>{ console.log('service:' + JSON.stringify(res)); return res; })
    //, catchError(this.handleError)
    );
    */
  }
}

这是我的component.html:

...........
<td *ngFor="let dateObj of monthlyCalendar?.dateObjList|async" class="phCell">
    <span *ngIf="dateObj.isPublicHoliday">PH</span>
</td>
...........

我想在 dateObj.isPublicHoliday 属性为 true 时显示 span 标签。

但是,浏览器提示我以下错误消息:

ERROR Error: InvalidPipeArgument: '[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]' for pipe 'AsyncPipe'

如果我也将“keyvalue”管道应用于 *ngFor,即

 <td *ngFor="let dateObj of monthlyCalendar?.dateObjList|async|keyvalue" class="phCell">

我收到以下错误消息:

Error: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe'

我怎样才能让它工作?

【问题讨论】:

标签: angular


【解决方案1】:

最后,我整合你所有的想法来解决这个问题,

我修改了 component.ts 以将monthlyCalendar 对象更改为可观察。

monthlyCalendar: Observable<MonthlyCalendar>;
................

ngOnInit() {
    this.monthlyCalendar = this.monthlyCalendarService.getMonthlyCalendar(null, null);
}

然后我按照 Adrian 的建议创建了一个名为 toArray 的管道。 我将component.html修改如下

<td *ngFor="let dateObj of (monthlyCalendar|async)?.dateObjList|toArray " class="alignCenter phCell borderCell dateCell">
    <span *ngIf=dateObj.isPublicHoliday>PH</span>
</td>

虽然正确显示“PH”,但浏览器提示“TypeError: Cannot read property 'dateObjList' of null”。
最后,下面的网页告诉我为什么安全导航运算符不能与异步管道一起使用。

Safe navigation operator on an async observable and pipe in Angular 2

所以,toArray 管道的最终版本如下:

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

@Pipe({
  name: 'toArray'
})
export class ToArrayPipe implements PipeTransform {
  transform(value, args: string[]): any {
    if (value != null) {
      return Object.values(value);
    }
  }
}

感谢您的所有帮助。

【讨论】:

    【解决方案2】:

    丢失异步管道,您的每月日历不是异步可观察对象。您在订阅后手动分配它。

    管道keyvalue 使用value 属性返回并带有2 个道具keyvalue 的对象导航到您的对象。

    检查管道keyvalue中的value属性

    dateObj.value.isPublicHoliday
    

    所以你的代码终于变成了

    <td *ngFor="let dateObj of (monthlyCalendar)?.dateObjList|keyvalue" class="phCell">
        <span *ngIf="dateObj.value.isPublicHoliday">PH</span>
    </td>
    

    编辑 2:

    Angular 推荐的正确方式

    如果你想在你的组件中像 observable 那样做

    monthlyCalendar$: Observable<any>; //declare your observable to use in template (Use a custom Interface to have data type as your response)
    
    ngOnInit(){
      this.monthlyCalendar$ = this.monthlyCalendarService.getMonthlyCalendar(null, null);
    }
    

    你的模板变成了

    <td *ngFor="let dateObj of (monthlyCalendar$|async)?.dateObjList|keyvalue" class="phCell">
        <span *ngIf="dateObj.value.isPublicHoliday">PH</span>
    </td>
    

    但要实现这一点,可能需要进行大量架构更改。但是您应该阅读更多关于显示来自 Angular 官方文档的服务数据的信息

    查看类似模拟https://stackblitz.com/edit/angular-pfhzrd的示例


    编辑 3:

    尽管选择了这样的数据结构,但很有可能您必须再次实现排序算法。

    您的服务可以这样修改 [注意]:建议服务器应该发送一个 dataObjList 数组而不是一个对象。

    return this.http.get('backend/getMonthlyCalendar.php', {params}).pipe(map((res: MonthlyCalendar) =>{
      res.dateObjList = Object.values(res.dateObjList).reduce((list, date) => [...list, date], []);
      //res.dateObjList.sort((a,b) => a.id - b.id); //Note you might lose your sort coming from backend so you need to sort it based on some key or anything
      return res;
    }));
    

    所以你的对象变成了如下所示的 dateObjList 数组

    {
        "monthName": "September 2019",
        "dateObjList": [
            {
                "dayOfWeek": "0",
                "isPublicHoliday": false,
                .............
            },
            {
                "dayOfWeek": "0",
                "isPublicHoliday": true,
                .............
            },
            .....................
        ]
    } 
    

    那么你的模板就不需要键值了:

    <td *ngFor="let dateObj of (monthlyCalendar$|async)?.dateObjList" class="phCell">
        <span *ngIf="dateObj.isPublicHoliday">PH</span>
    </td>
    

    【讨论】:

    • 浏览器提示如下错误: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe' 我已经把服务和组件的源代码都包含了供大家参考。
    • 似乎有人是对的,每月日历不是可观察的
    • @TheKNVB 检查更新的答案。虽然你应该直接将 observables 分配给这样的模板 stackblitz.com/edit/angular-pfhzrd
    • 我不确定monthlyCalendar 是否是可观察的。我已将服务和组件添加到我的问题中以供参考。
    • 它在错误的单元格中显示 PH。 “PH”应显示在单元格 14 中,但显示在单元格 6 中。
    【解决方案3】:

    问题是您的数据不是可观察的,并且您使用的是异步管道。如果 monthlyCalendar?.dateObjList 可以观察到,则异步管道可以工作。

    <td *ngFor="let dateObj of monthlyCalendar?.dateObjList" class="phCell">
        <span *ngIf="dateObj.isPublicHoliday">PH
        </span> 
    </td>
    

    否则你可以这样做。

    <td *ngFor="let dateObj of (monthlyCalendar| async)?.dateObjList" class="phCell">
        <span *ngIf="dateObj.isPublicHoliday">PH
        </span> 
    </td>
    

    您可以从here 了解更多信息

    【讨论】:

      【解决方案4】:

      试试这个方法

      @Pipe({name: 'keys'})
      export class KeysPipe implements PipeTransform {
        transform(value, args:string[]) : any {
          let keys = [];
          for (let key in value) {
            keys.push({key: key, value: value[key]});
          }
          return keys;
        }
      }
      
      
      <td *ngFor="let dateObj of monthlyCalendar?.dateObjList|keys|async" class="phCell">
      <span *ngIf="dateObj.isPublicHoliday">PH</span>
      

      更多详情,请访问:How to display json object using *ngFor

      【讨论】:

        【解决方案5】:

        ngFor 迭代数组而不是对象,你可以将数据分配给数组

        dates = Object.values(data.dateObjList);
        

        然后你可以对日期数组进行 ngFor。

        如果您想使用异步管道,您需要创建一个将对象转换为数组的管道。

        import { Pipe, PipeTransform } from '@angular/core';
        
        @Pipe({
          name: 'toArray'
        })
        export class ToArrayPipe implements PipeTransform {
          transform(value: any): any {
            return Object.values(value);
          }
        }
        

        并与它一起使用

        *ngFor="let dateObj of (monthlyCalendar | async).dateObjList| toArray"
        

        【讨论】:

        • 我已经应用了keyvalue,但是你可以看到上面的错误信息。
        • monthlyCalendar 是可观察的吗?如果是这样,它应该按照命名标准命名为monthlyCalendar$,然后您需要使用 (monthlyCalendar | async).dateObjList | toArray
        • 我不确定monthlyCalendar 是否可观察。我已将服务和组件添加到我的问题中。
        • 我怎样才能使每月日历可观察?
        • 而不是订阅您的服务,您将返回的 observable 分配给属性monthlyCalendar$ = this.monthlyCalendarService.getMonthlyCalendar(null, null) 然后您可以在该属性上使用异步管道。无需订阅。