1

I have a problem that unfortunately I cannot resolve.

My API returns me a list of data like this:

[
  {grade: "Grade A", id: 1, ifsGrade: "A1XX", ifsType: "01XX", points: 22, type: "Type_1"},
  {grade: "Grade B", id: 2, ifsGrade: "B1XX", ifsType: "02XX", points: 15, type: "Type_1"},
  {grade: "Grade C", id: 3, ifsGrade: "C1XX", ifsType: "03XX", points: 1,  type: "Type_1"},
  {grade: "Grade A", id: 4, ifsGrade: "A2XX", ifsType: "04XX", points: 23, type: "Type_2"},
  {grade: "Grade B", id: 5, ifsGrade: "B2XX", ifsType: "05XX", points: 26, type: "Type_2"}
]

And I group my data by type like this:

Array.prototype.groupBy = function(k) {
   return this.reduce((acc, item) => (acc[item[k]] = [...(acc[item[k]] || []), item], acc), {});
};

var TABLE_DATA = Object.values(API_DATA.groupBy("type"));

[
  [
    {grade: "Grade A", id: 1, ifsGrade: "A1XX", ifsType: "01XX", points: 22, type: "Type_1"},
    {grade: "Grade B", id: 2, ifsGrade: "B1XX", ifsType: "02XX", points: 15, type: "Type_1"},
    {grade: "Grade C", id: 3, ifsGrade: "C1XX", ifsType: "03XX", points: 1,  type: "Type_1"}
  ],
  [
    {grade: "Grade A", id: 4, ifsGrade: "A2XX", ifsType: "04XX", points: 23, type: "Type_2"},
    {grade: "Grade B", id: 5, ifsGrade: "B2XX", ifsType: "05XX", points: 26, type: "Type_2"}
  ]
]

I would like to be able to display the data in a Angular Material mat-table like this: enter image description here

So I do have a list for my dataSource but the elements of this list are lists too. So I don't know how to display the items correctly.

So for the table columns you would need something dynamic like this, but I don't know how:

<ng-container matColumnDef="{{column}}" *ngFor="let column of definedColumns">
  <th mat-header-cell *matHeaderCellDef> {{column}} </th>
  <td mat-cell *matCellDef="let element"> {{element[column]}} </td>
</ng-container>

Stackblitz link here

Thanks for your help

Quentin
  • 1,865
  • 2
  • 24
  • 40
  • 1
    Have you looked at [reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)? I think this might help: https://stackoverflow.com/a/43780968/1644624 – Edwin Otten Jul 22 '20 at 13:31

2 Answers2

1

You will need to iterate through your data and map the different values to your new object.

Here's a working stack example https://stackblitz.com/edit/datasource-mat-table-with-group-by-ay2rbh

And the relevant functions from the stackblitz

  convertedData: ElementType[];

  // creates "gradeX" from "Grade X"
  convertGradeToGradeKey(grade: string): string{
    return `grade${grade.split(" ")[1]}`;
  }

  // probably worth typing this "data" properly as well...
  mapDataFromApi(data: any[]): void {
  // using a map so we don't have to iterate the whole list to find type === "someType" every time
  let mappedData = new Map<string, ElementType>();
   
   data.forEach(val => {
     // get the existing value from the map, or ceate a "new" one if its not there
     let currentVal = mappedData.get(val.type) || {type: val.type, gradeA: undefined, gradeB: undefined, gradeC: undefined};
     // get the "grade key" from the string value
     const gradeKey = this.convertGradeToGradeKey(val.grade);
     // get the new value, which will be the curent value + this row's "poiints"
     const newGradeValue: number = (currentVal[gradeKey] || 0) + val.points;
     // merge the current value object with the new property
     currentVal = {...currentVal, [gradeKey]: newGradeValue };
     // add upate the value in the set
     mappedData.set(val.type, currentVal);
   })
   // create a normal array from that mapped data
   this.convertedData = [...mappedData].map(([key, val]) => val);
  }
cjd82187
  • 3,468
  • 3
  • 15
  • 19
  • Thank you for your answer, I modified my post. I don’t want to modify my objects because I need all attributes to modify or delete them. So I found a solution to group them by type in a list. But the display in the table is complicated – Quentin Jul 23 '20 at 09:15
  • Sorry, I don't really get what you are trying to do now in your update. If you want to display the data in a table, it needs to be a single array. You were originally posting an aggregate table, but now I'm not sure what it is. – cjd82187 Jul 23 '20 at 12:37
  • You will need to do something like I am if you want to display them in the table you want. I don't modify the original data if you look at my stackblitz, the original data is still in a different variable if you need to update them. Not sure what your add/delete buttons would do since the row has no idea which original value you are trying to update – cjd82187 Jul 23 '20 at 12:40
  • The data displayed in your table does not have all the original attributes (id, ifsType ...). I will need it for editing and deleting, because several rows will be affected in the database. – Quentin Jul 23 '20 at 13:29
  • This is why I modified my post with my data grouped by 'type' this time. Each row of the table is therefore a list of data, but only certain values ​​should be displayed in the correct columns. In my answer that I published when I would like to modify a row I will retrieve the list of all the data corresponding to a type in order to be able to modify or delete them. – Quentin Jul 23 '20 at 13:29
0

Data initialization with grouping by type :

ngOnInit() {
    this.API_DATA = [
      {grade: "Grade A", id: 1, ifsGrade: "A1XX", ifsType: "01XX", points: 22, type: "Type_1"},
      {grade: "Grade B", id: 2, ifsGrade: "B1XX", ifsType: "02XX", points: 15, type: "Type_1"},
      {grade: "Grade C", id: 3, ifsGrade: "C1XX", ifsType: "03XX", points: 1,  type: "Type_1"},
      {grade: "Grade A", id: 4, ifsGrade: "A2XX", ifsType: "04XX", points: 23, type: "Type_2"},
      {grade: "Grade B", id: 5, ifsGrade: "B2XX", ifsType: "05XX", points: 16, type: "Type_2"}
    ];

    const key = 'type'
    const dataTable = Object.values(this.API_DATA.reduce((acc, item) => (acc[item[key]] = [...(acc[item[key]] || []), item], acc), {}));

    this.dataSource = dataTable;
  }

mat-table Angular material with dynamic columns :

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
  
  <ng-container matColumnDef="{{column}}" *ngFor="let column of definedColumns; let index = index;">
    <th mat-header-cell *matHeaderCellDef> {{column}} </th>
    <td mat-cell *matCellDef="let elements">
      <span *ngFor="let element of elements; let first = first;">
        <span *ngIf="index == 0 && first">{{element.type}}</span>
        <span *ngIf="element.grade == column">{{element.points}}</span>
      </span>
    </td>
  </ng-container>

  <ng-container matColumnDef="action">
    <th mat-header-cell *matHeaderCellDef class="text-right">
        <button mat-icon-button (click)="log('add')">
            <mat-icon color="accent">add_circle_outline</mat-icon>
        </button>
    </th>
    <td mat-cell *matCellDef="let gradingIndex" class="text-right">
        <button mat-icon-button>
            <mat-icon color="primary" (click)="log(gradingIndex)">edit</mat-icon>
        </button>
        <button mat-icon-button (click)="log(gradingIndex)">
            <mat-icon color="warn">delete</mat-icon>
        </button>
    </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Result :

enter image description here

Stackblitz : here

Quentin
  • 1,865
  • 2
  • 24
  • 40