Search code examples
jsonangulardynamicngfor

Angular: linking data to dynamic table using *ngFor


I am creating a dynamic table. The problem with my json structure below is that the Students object does not contain Subjects array within the object, but rather Students[] and Subjects[] are defined through Links[], where there is link defined for student a subject.

The output of the table should look like this: enter image description here

Do you have any suggestions how to create *ngFor in ??? area that links each student to its subjects?

The json structure looks like this:

export class StudentsAndSubjects {
 info: SchoolInformation;
}

export class SchoolInformation {
 students: Students[];
 subjects: Subjects[];
 links: Links[];
}

export class Students {
 id: string;   //example: student_1
 name: string;
 surname: string;
};
export class Subjects{
 id: string;   //example: subject_1
 name: string;
};
export class Links{
 studentId: string; //example: student_1
 subjectId: string; //example: subject_1
};

The table structure:

@Component({
 selector: 'app-student-and-subjects',
 template: `
   <table>
     <tr *ngFor="let row of rowHeaders">
        <td *ngFor="let item of data.info.students.name">
           <span *ngIf="row !== 'Subjects'">{{ item[row] }}</span>
        </td>
        <table *ngIf="row === 'Subjects'">
            <tr **???**>
               <td>{{ subject}}</td>
            </tr>
        </table>
     </tr>
   </table>
`;
})
export class StudentsAndSubjects {
 @Input() data: StudentSubjectData;
 readonly rowHeaders = ['Student','Subjects']
}

Solution

  • First, I think you need to restructure your table a bit. Since the table of subjects is associated to a particular student, it should probably live inside the td that contains that student's data:

    <table>
      <tr *ngFor="let row of rowHeaders">
        <td *ngFor="let student of data.info.students"> <--- each td contains data for a student
          <span *ngIf="row !== 'Subjects'">{{ student.name }}</span>
    
          <table *ngIf="row === 'Subjects'"> <--- move this table inside the td
            <tr>
              <td *ngFor="let subject of getSubjectsForStudent(student)"> <-- the *ngFor you were missing
                {{ subject.name }}
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    

    Also note that I changed your *ngFor="let item of data.info.students.name" so it iterates over data.info.students rather than data.info.students.name since you need the student instance data (and data.info.students.name isn't a collection).

    And to display the student name, I replaced {{ item[row] }} with {{ student.name }}.

    Then, to display the subjects for that student, you would need to create a method that accepts the student object (or just its id) and then parses the data and returns the student's associated subjects.

    That method is what you would need in the missing *ngFor that you're asking about.

    (Alternatively, of course, you could transform the data ahead of time into a mapping object that maps the students to their subjects.)

    So the missing *ngFor that you're asking about would look something like this:

    <td *ngFor="let subject of getSubjectsForStudent(student)">
    

    Note that it's on the td, not the tr like in your markup. That's because each subject has its own cell, not its own row—all subjects are in the same row.

    Here's a StackBlitz showing this approach.