简体   繁体   中英

Angular 7 material table with dynamic headers and cells

considering that I get an array of objects from an API like this (the user has select boxes to chose the year, the type and input a value) :

[
  {
    "year": 2019,
    "type": "Salaries",
    "value": 100
  },
  {
    "year": 2019,
    "type": "Projects",
    "value": 78
  },
  {
    "year": 2018,
    "type": "Projects",
    "value": 500
  },
  {
    "year": 2018,
    "type": "Others",
    "value": 500
  },
]

I need to display this data per year like this :

| Year | Salaries | Pojects | Others |
-------------------------------------
| 2019 |   100    | 78      |        |
-------------------------------------
| 2018 |          | 500     | 500    |

The part that I couldn't figure out is how to loop through the array to build the array headers, then for each year put the right value on the corresponding type...thanks for your help.

To achieve your goal you need to create a new array by using data which received from the API. To give my solution, I will assume you received data array like below from the API.

[{ "year": 2019, "type": "Salaries", "value": 100 },
 { "year": 2019, "type": "Projects", "value": 78 },
 { "year": 2018, "type": "Projects", "value": 500 },
 { "year": 2018, "type": "Others", "value": 500 },
 { "year": 2019, "type": "Others", "value": 230 },
 { "year": 2018, "type": "Salaries", "value": 900 }]

To show above data in a table like in your question, you have to convert it to meaningful array like below.

[{ "year": 2019, "salary": 100, "project": 78, "other": 230 },
    { "year": 2018, "salary": 900, "project": 500, "other": 500 }]

I assume there are no duplicate records for a certain year with same type like below.

{ "year": 2019, "type": "Salaries", "value": 100 }
{ "year": 2019, "type": "Salaries", "value": 200 }

If there are duplicates like above last element's value will be taken as the value .

You have to implement new function to create the displayData array as follows.

TypeScript

export class MyComponent  {
  data = [
 { "year": 2019, "type": "Salaries", "value": 100 },
 { "year": 2019, "type": "Projects", "value": 78 },
 { "year": 2018, "type": "Projects", "value": 500 },
 { "year": 2018, "type": "Others", "value": 500 },
 { "year": 2019, "type": "Others", "value": 230 },
 { "year": 2018, "type": "Salaries", "value": 900 }];

displayData: any = [];

 constructor() {
   this.createDisplayData()
 }

 createDisplayData(): void {

   for (let i = 0; i < this.data.length; i++) {

      let type: string;

      if (this.data[i].type === "Salaries")
        type = 'salary';
      else if (this.data[i].type === "Projects") 
        type = 'project';
      else 
        type = 'other';

     const found = this.displayData.find(v => v.year === this.data[i].year);

     if (!found) {
       let object = {};
       object['year'] = this.data[i].year;
       object[type] = this.data[i].value
       this.displayData.push(object);
     }
     else {
       found[type] = this.data[i].value;
     }
   }
 }
}

HTML

<table style="width: 100%">
  <tr>
    <th>Year</th>
    <th>Salaries</th>
    <th>Pojects</th>
    <th>Others</th>
  </tr>
  <tr *ngFor="let data of displayData">
    <td style="text-align: center">{{data.year}}</td>
    <td style="text-align: center">{{data.salary}}</td>
    <td style="text-align: center">{{data.project}}</td>
    <td style="text-align: center">{{data.other}}</td>
  </tr>
</table>

Find StackBlitz Demo Here .

Update on suggestion by Comment.

Other way you can do this is using Array.reduce() function instead of for loop is as follows.

TS

 createDisplayData(): void {

   this.displayData = this.data.reduce((accumulator, currentValue) => {

     const found = accumulator.find(v => v.year === currentValue.year);
     if (!found) {

       let object = {};
       object['year'] = currentValue.year;
       object[currentValue.type] = currentValue.value;
       accumulator.push(object);
     } else {

       found[currentValue.type] = currentValue.value;
     }
     return accumulator;
   }, []);
 }

HTML

<table style="width: 100%">
  <tr>
    <th>Year</th>
    <th>Salaries</th>
    <th>Pojects</th>
    <th>Others</th>
  </tr>
  <tr *ngFor="let data of displayData">
    <td style="text-align: center">{{data.year}}</td>
    <td style="text-align: center">{{data.Salaries}}</td>
    <td style="text-align: center">{{data.Projects}}</td>
    <td style="text-align: center">{{data.Others}}</td>
  </tr>
</table>

Find StackBlitz Demo .

Since you want to display data per year, you can use the year as your unique identifier. Just loop through the objects and create a new object like below:

let dataPerYear = {}

response
    .forEach(function (data) {
        if (!dataPerYear[data.year]) {
            dataPerYear[data.year] = {
                year: data.year
            }
        }

        dataPerYear[data.year][data.type] = [data.value]
    })

that should result in

{
    2019: {
        year: 2019,
        Salaries: 100,
        Projects: 78
    }
}

then, if you want it as an array:

let arrayData = Object.values(result)

will result in:

[
    {
        year: 2019,
        Salaries: 100,
        Projects: 78
    }
]

You could map your collection into the shape you want for that table using reduce() .

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

Then simply map the fields to the Material table like you normally would.

 const data = [ { "year": 2019, "type": "Salaries", "value": 100 }, { "year": 2019, "type": "Projects", "value": 78 }, { "year": 2018, "type": "Projects", "value": 500 }, { "year": 2018, "type": "Others", "value": 500 }, ] const mappedData = data.reduce((acc, item) => { const match = acc.find(x => x.year === item.year); const transformed = { year: item.year, projects: item.type === "Projects" ? item.value : 0, salaries: item.type === "Salaries" ? item.value : 0, others: item.type === "Others" ? item.value : 0 }; if (match) { match.projects += transformed.projects; match.salaries += transformed.salaries; match.others += transformed.others; } else { acc.push(transformed); } return acc; }, []); console.log(mappedData); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM