How to render a single list of records in a nested manner in Angular? I have an array of appointment records. I want to group the appointments by day. This is simple if you're generating the HTML yourself with nested loops in Javascript but I don't know how to do this in Angular *ngFor from a single list (nesting *ngFors with multiple lists is easy enough).
Here is a simplified version of the problem. First, the data model.
export interface Appointment {
scheduled: Date;
client: string;
}
appointments$: Observable<Appointment[]>;
Here is how I would like to render it, grouped by day, and then a list of client names below each day.
Then finally here is the HTML to generate that picture.
<ion-item-group>
<ion-item-divider>
<ion-label>MON 15</ion-label>
</ion-item-divider>
<ion-item>
<ion-label>Alfred Futter</ion-label>
</ion-item>
<ion-item>
<ion-label>Around the Trumpet</ion-label>
</ion-item>
</ion-item-group>
<ion-item-group>
<ion-item-divider>
<ion-label>TUE 16</ion-label>
</ion-item-divider>
<ion-item>
<ion-label>Antonio Taco</ion-label>
</ion-item>
<ion-item>
<ion-label>Berglunds</ion-label>
</ion-item>
<ion-item>
<ion-label>Blondel and Sons</ion-label>
</ion-item>
</ion-item-group>
So now the problem is I have one array of appointments which is typically iterated in Angular like this:
<ion-item-group *ngFor="let appt of appointments$ | async">
...
<ion-item-group>
But this just generates the outer list of ion-item-group. How do I also generate the inner subgroup of ion-items under each day using the appts.scheduled field to group?
Thank you.
Edit: my own solution per request of @E.Maggini below. First I munged the service itself to group the appointments by day, like this:
appointments = Object.values(_.groupBy(appointments, a => a.scheduled.getDate()));
(yes I know just getDate() is not enough, this is just for proof of concept)
Then the following HTML suffices to render it exactly as desired:
<ion-list>
<ion-item-group *ngFor="let apptsOneDay of appointments$ | async">
<ion-item-divider>
<ion-label>{{apptsOneDay[0].scheduled}}</ion-label>
</ion-item-divider>
<ion-item *ngFor="let appt of apptsOneDay">
<ion-label>{{appt.name}}</ion-label>
</ion-item>
</ion-item-group>
</ion-list>
I would much prefer to operate on the Observable<Appointments[]> returned by the service using RXJS operators per the suggestion @GaurangDhorda but I can't get the operator chain to work.
CodePudding user response:
Complete Working demo in this Stackblitz Link
Basically, All you need to do pipe your appointment observable with groupBy, mergeMap, and toArray rxjs operators so, that you got your groupBy date data and and You can use this data into the template and traverse using nested ngFor. Here is your observable pipe code..
appointment$: Observable<appointmentObservable[]> = of(
this.appointmentData).pipe(
mergeMap((parentData) => parentData),
groupBy((parentGroup) => parentGroup.scheduled.toLocaleDateString()),
mergeMap((group) => this.reducePipe(group)),
toArray()
);
reducePipe(group: GroupedObservable<string, appointment>) {
return group.pipe(
reduce(
(acc, cur) => {
acc.clients.push(cur);
return acc;
},
{ scheduled: new Date(group.key), clients: [] }
)
);
}
In above code this line parentGroup.scheduled.toLocaleDateString()
of groupBy operator is getting schedule date type data and we convert it to localString and pass it to next mergeMap pipe. Then we use reduce operator and again we convert stringDate to dateFormat date using scheduled: new Date(group.key)
This line.
Now HTML is...
<ng-container *ngIf="appointment$ | async as appointments">
<ion-item-group *ngFor="let appointment of appointments">
<ion-item-divider>
<ion-label>{{ appointment.scheduled | date: 'EEE dd' }}</ion-label>
</ion-item-divider>
<ion-item *ngFor="let userNames of appointment.clients">
<ion-label>{{ userNames.client }}</ion-label>
</ion-item>
</ion-item-group>
</ng-container>
In above html we are using appointment.scheduled | date: 'EEE dd'
date pipe of angular with format to transform date to your need like MON 21
. Thank You.