0

This is my "monthlyCalendar" object:

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

This is my component.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);
      },
    );
  }
.........

This is my monthlyCalendarService.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)
    );
    */
  }
}

This is my component.html:

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

I want to show the span tag when the dateObj.isPublicHoliday attribute is true.

However, the browser prompts me the below error message:

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'

if I apply the "keyvalue" pipe to the *ngFor also, i.e.

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

I got the following error message:

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

How can I make it works?

Dharman
  • 30,962
  • 25
  • 85
  • 135
The KNVB
  • 3,588
  • 3
  • 29
  • 54
  • 2
    Possible duplicate of [access key and value of object using \*ngFor](https://stackoverflow.com/questions/35534959/access-key-and-value-of-object-using-ngfor) – Paul Rooney Sep 20 '19 at 03:30

5 Answers5

2

Well the problem is your data is not an observable and you are using async pipe. Async pipe works if monthlyCalendar?.dateObjList would have been observable.

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

Or else you can do it like this.

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

You can read more about it from here

Arghya Saha
  • 5,599
  • 4
  • 26
  • 48
1

ngFor iterates arrays not object, you can assign the data to an array

dates = Object.values(data.dateObjList);

Then you can ngFor on the dates array.

If you want to use the async pipe you will need to create a pipe that converts an object to an array.

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

@Pipe({
  name: 'toArray'
})
export class ToArrayPipe implements PipeTransform {
  transform(value: any): any {
    return Object.values(value);
  }
}

and use it with

*ngFor="let dateObj of (monthlyCalendar | async).dateObjList| toArray"
Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
  • I have applied the keyvalue, but you can see the error message above. – The KNVB Sep 20 '19 at 03:31
  • Is monthlyCalendar an observable? If so it should be named monthlyCalendar$ as is the naming standard and then you would need to use (monthlyCalendar | async).dateObjList | toArray – Adrian Brand Sep 20 '19 at 03:35
  • I am not sure whether monthlyCalendar is observable. I have added both the service and component to my question. – The KNVB Sep 20 '19 at 04:08
  • How can I make the monthlyCalendar observable? – The KNVB Sep 20 '19 at 04:10
  • Instead of subscribing to your service you assign the observable returned to a property monthlyCalendar$ = this.monthlyCalendarService.getMonthlyCalendar(null, null) and then you can use the async pipe on that property. No need to subscribe. – Adrian Brand Sep 20 '19 at 04:17
  • Although it show the "PH" correctly, the browser prompt "TypeError: Cannot read property 'dateObjList' of null" – The KNVB Sep 20 '19 at 04:25
  • Add the safe navigation operator in (monthlyCalendar | async)?.dateObjList – Adrian Brand Sep 20 '19 at 04:27
  • It does not help. – The KNVB Sep 20 '19 at 06:32
1

Lose the async pipe your monthlyCalendar is not an async observable. you are manually assigning it after subscribing.

pipe keyvalue returns and object with 2 props key and value using the attribute value navigate to your object.

Check the value attribute from pipe keyvalue

dateObj.value.isPublicHoliday

So your code becomes finally

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

Edit 2:

The right way recommended by Angular

If you want to do it like an observable in your component

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);
}

and your template becomes

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

But there might be lot of architectural changes required to accomplish this. But you should read more about displaying data from services by Angular official docs

Check an example like simulation https://stackblitz.com/edit/angular-pfhzrd


Edit 3:

Although choosing a data structure like this it is highly likely you have to implement a sorting algorithm again.

Your service can be modified like this [NOTE]: It is recommended that the server should be sending an array of dataObjList and not an object.

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;
}));

So your object becomes as dateObjList array like this below

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

then your template you don't need key value:

<td *ngFor="let dateObj of (monthlyCalendar$|async)?.dateObjList" class="phCell">
    <span *ngIf="dateObj.isPublicHoliday">PH</span>
</td>
joyBlanks
  • 6,419
  • 1
  • 22
  • 47
  • The browse prompt the following error: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe' I have included both service and component source code for your reference. – The KNVB Sep 20 '19 at 03:50
  • It seems someone was right monthlyCalendar is not an observable – joyBlanks Sep 20 '19 at 03:51
  • @TheKNVB check updated answer. though you should be directly assign observables to template like this https://stackblitz.com/edit/angular-pfhzrd – joyBlanks Sep 20 '19 at 03:55
  • I am not sure whether monthlyCalendar is observable. I have added both the service and component to my question for reference. – The KNVB Sep 20 '19 at 04:09
  • It displays PH in the wrong cell. The "PH" should be shown in cell 14, however, it is shown in cell 6. – The KNVB Sep 20 '19 at 04:27
  • should be a data issue – joyBlanks Sep 20 '19 at 04:31
  • it seems that key is sorted alphanumeric so 1,2,10,20 -> 1,10,2,20. you have to write orderly or sorting custom. I think the best suitable option into change api, or manipulate data after the response – joyBlanks Sep 20 '19 at 04:44
1

Try this way

@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>

More details, please visit : How to display json object using *ngFor

Ramesh Rajendran
  • 37,412
  • 45
  • 153
  • 234
0

Finally, I consolidate all of your ideas to fix the problem,

I modify the component.ts for changing the monthlyCalendar object to be observable.

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

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

And then I create a pipe called toArray as Adrian suggested. I modify the component.html as the following

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

Although it shows the "PH" correctly, the browser prompt "TypeError: Cannot read property 'dateObjList' of null".
Lastly, the following web page told me that why the safe navigation operator does not work with the async pipe.

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

So, the final version of toArray pipe as the following:

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);
    }
  }
}

I am appreciated for all your help.

The KNVB
  • 3,588
  • 3
  • 29
  • 54