Search code examples
angulartypescriptngforangular14arrayofarrays

How to iterate an array of arrays of objects with *ngFor in Angular 14?


I know similar questions were asked, but none of them helped me with my problem.

I have a dynamic array of objects I get from server (more draws in the future):

0: {id: 19, draw: 8, colour: 'Black', person1: 'John', person2: 'Jane'}
1: {id: 18, draw: 8, colour: 'Red', person1: 'Mike', person2: 'Mary'}
2: {id: 20, draw: 8, colour: 'White', person1: 'Sam', person2: 'Leah'}
3: {id: 16, draw: 7, colour: 'Red', person1: 'Bob', person2: 'Nora'}
4: {id: 17, draw: 7, colour: 'Black', person1: 'Tom', person2: 'Tina'}

I reorder the array into multiple arrays based on draw number:

getDrawHistory(): void {
  this.pairApiService.getDrawHistory().subscribe(pairs => {
    this.pairs = pairs;

    const groupBy = (key: string) => (array: any[]) =>
      array.reduce((objectsByKeyValue: { [x: string]: any; }, obj: { [x: string]: any; }) => {
        const value = obj[key];
        objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
        return objectsByKeyValue;
      }, {})

    const groupByDraw = groupBy('draw');

    this.pairsByDraw = JSON.parse(JSON.stringify({ pairsByDraw: groupByDraw(this.pairs) }, null, 2));
  })
}

Where I get:

pairsByDraw:
    7: Array(2)
        0: {id: 16, draw: 7, colour: 'Red', person1: 'Bob', person2: 'Nora'}
        1: {id: 17, draw: 7, colour: 'Black', person1: 'Tom', person2: 'Tina'}
    8: Array(3)
        0: {id: 18, draw: 8, colour: 'Red', person1: 'Mike', person2: 'Mary'}
        1: {id: 19, draw: 8, colour: 'Black', person1: 'John', person2: 'Jane'}
        2: {id: 20, draw: 8, colour: 'White', person1: 'Sam', person2: 'Leah'}

What I want is to iterate the array of arrays of objects with *ngFor and get something like (possibly dynamically generated mutliple tables):

Red Black
Bob Tom
Nora Tina
Red Black White
Mike John Sam
Mary Jane Leah

How is it done?

I tried:

*ngFor="let item of pairsByDraw.pairsByDraw | keyvalue"
{{item.key}}: {{item.value}}

but cannot get anything from item.value other than [object Object].

EDIT: Changing the brackets from in:

const groupBy = (key: string) => (array: any[]) =>
        array.reduce((objectsByKeyValue: { [x: string]: any; }, obj: { [x: string]: any; }) => {
          const value = obj[key];
          objectsByKeyValue[value] = (objectsByKeyValue[value] || []).concat(obj);
          return objectsByKeyValue;
        }, []) // before: }, {})

from {} to [] seems to make some difference. Will dig into.


Solution

  • Maybe not the best solution, but it works (except sort, I don't know why it doesn't sort by color).

    HTML:

    <table *ngFor="let table of tables">
        <tr>
            <th *ngFor="let column of table.columns">
                {{column}}
            </th>
        </tr>
    
        <tr *ngFor="let personLevel of table.personsLevels">
            <td *ngFor="let column of table.columns">
                {{personLevel[column]}}
            </td>
        </tr>
    </table>
    

    Typescript:

    public group() {
            const groupBy = (data) => {
                // Create a dictionary of groups.
                const map = new Map();
                data.forEach(item => {
                    const groupKey = item['draw'];
                    const table = map.get(groupKey);
                    // first we group by draw, it will be our tables
                    if (table == null) {
                        const newTable = {
                            columns: [item.colour], // we also need columns
                            personsLevels: groupPersonsByLevels(item, item.colour) // and we need rows
                        }
                        map.set(groupKey, newTable);
                    } else {
                        table.columns.push(item.colour);
                        table.personsLevels = groupPersonsByLevels(item, item.colour, table.personsLevels);
                    };
                })
                for (const key in map) {
                    map[key].personsLevels.sort((a, b) => a.level - b.level);
                    map[key].columns.sort((a, b) => a - b);
                }
                // Transform it back into a list of groups.
                return [...map.values()];
            }
    
    // rows is the persons group by person level 
    // (if we have 3 levels for example it would be 3 rows.
    // Each element in rows puts in his own color
    
            const groupPersonsByLevels = (item, column, personsLevels = []) => {
                for (const key in item) {
                    if (key.match('person')) {
                        const level = key.replace( /^\D+/g, '');
                        const existingPersonLevel = personsLevels.find(el => el.level === level);
                        if (existingPersonLevel) {
                            existingPersonLevel[column] = item[key];
                        } else {
                            personsLevels.push({
                                level,
                                [column]: item[key]
                            });
                        };
                    }
                }
                return personsLevels;
            }
    
            this.tables = groupBy(this.pairs);
        }