Rolando
Rolando

Reputation: 62626

How to output groups with *ngFor in angular based on property?

Assume I have the following object 'myarr':

[
    {'name':'Mary', 'chapter':'Chapter 1'},
    {'name':'Joseph', 'chapter':'Chapter 1'},
    {'name':'John', 'chapter':'Chapter 2'},
    {'name':'Carl', 'chapter':'Chapter 3'},
    {'name':'Jacob', 'chapter':'Chapter 3'}
]

Is it possible to make it so that I can output the following:

Chapter 1

Mary

Joseph

Chapter 2

John

Chapter 3

Carl

Jacob

If so, what is a good way to accomplish this with just one list? I don't want to hardcode Chapter 1, Chapter 2, Chapter 3. I would like to infer from the data.

<div *ngFor="let name of myarr">
    // two ngFor to get the chapter?
    {{name}}
</div>

Upvotes: 5

Views: 7973

Answers (3)

inorganik
inorganik

Reputation: 25525

Use a function to determine whether to display the chapter. Using a trackBy function in the ngFor will help performance.

component:

  lastChapter = '';
  myarr = [
    {'name':'Mary', 'chapter':'Chapter 1'},
    {'name':'Joseph', 'chapter':'Chapter 1'},
    {'name':'John', 'chapter':'Chapter 2'},
    {'name':'Carl', 'chapter':'Chapter 3'},
    {'name':'Jacob', 'chapter':'Chapter 3'}
  ];

  displayChapter(item): boolean {
    if (item.chapter !== this.lastChapter) {
      this.lastChapter = item.chapter;
      return true;
    } else {
      return false;
    }
  }

  trackChapter(index: number; item: any): string {
    return item.name + item.chapter;
  }

template:

<div *ngFor="let item of myarr; trackBy: trackChapter">
  <h1 *ngIf="displayChapter(item)">{{ item.chapter }}</h1>
  <p>{{ item.name }}</p>
</div>

Stackblitz

Upvotes: -2

Nadhir Falta
Nadhir Falta

Reputation: 5257

Check this example here: https://stackblitz.com/edit/group-by-inangular

You need to group your items by chapter first, and that should be done in your ts file like this:

groupArr = this.myarr.reduce((r,{group})=>{
        if(!r.some(o=>o.chapter==chapter)){
          r.push({chapter,groupItem:this.myarr.filter(v=>v.chapter==chapter)});
    }
    return r;
    },[]);

and then in your html do this:

   <table>
     <tr>
         <th>ID</th>
         <th>Name</th>
      </tr>
        <tbody *ngFor="let item of groupArr">
           <ng-container>
             <tr>
                <td colspan="2"><b>{{item.group}}</b></td>
             </tr>
             <tr *ngFor="let value of item.groupItem">
               <td>{{value.name}}</td>
             </tr>
           </ng-container>
         </tbody>
   </table>

Upvotes: 1

Vitalii Chmovzh
Vitalii Chmovzh

Reputation: 2943

There is one "dirty" hack that you can make in order to achieve it. Personally I recommend you to go with the grouping of items, but here is the 100% working solution in case you want to stick to a single list.

Steps are:

  1. Sort your array by chapter
  2. Rely on having different chapter for the previous item when rendering header

TypeScript

export class AppComponent  {
  arr = [
    {'name':'Mary', 'chapter':'Chapter 1'},
    {'name':'Joseph', 'chapter':'Chapter 1'},
    {'name':'John', 'chapter':'Chapter 2'},
    {'name':'Carl', 'chapter':'Chapter 3'},
    {'name':'Jacob', 'chapter':'Chapter 3'}
  ];

  constructor() {
    this.arr = this.arr.sort((a,b) => a.chapter > b.chapter ? 1 : -1);
  }
}

HTML

<div *ngFor="let item of arr; let index = index">
  <h3 *ngIf="!arr[index-1] || item.chapter !== arr[index-1].chapter">{{item.chapter}}</h3>
  {{item.name}}
</div>

StackBlitz: https://stackblitz.com/edit/angular-4y6anf

Upvotes: 13

Related Questions