angularserviceangular-material-tablererender

Newly created item show only after refresh in mat table


I'm new to Angular and I've been struggling a bit with this:

I have an Angular Material table whose dataSource, as you can see in the code below, is a variable named "students". This variable contains the students data provided by a students service. If I update or delete a student from the table, it will automatically re-render and show the updated data correctly. My problem lies in that when I create a new student the table won't re-render and it'll only show the newly created student after refreshing. How can I fix this? Thanks in advance.

This is what I've got so far...

student.service.ts:

export class StudentService {

  public element: Student;

  private studentsSubject: BehaviorSubject<Student[]> = new BehaviorSubject<Student[]>([]);
  public students$: Observable<Student[]> = this.studentsSubject.asObservable();

  private modeSubject: BehaviorSubject<string> = new BehaviorSubject('Crear');
  public mode$: Observable<string> = this.modeSubject.asObservable();
  
  constructor(
    private httpClient: HttpClient
  ) {
    this.getStudentsFromAPI().subscribe(students => {
      this.studentsSubject.next(students);
    })
  }

  getStudentsFromAPI(): Observable<Student[]> {
    return this.httpClient.get<Student[]>('https://63cc20169b72d2a88e0893c6.mockapi.io/alumnos');
  }

  createStudent(student: Student): void {
    let newId = this.studentsSubject.getValue().length + 1;
    let newStudent = {...student, id: newId, commissionId: null};
    let newList = this.studentsSubject.getValue();
    
    this.httpClient.post('https://63cc20169b72d2a88e0893c6.mockapi.io/alumnos', newStudent).subscribe(_ => {
      newList.push(newStudent);
      this.studentsSubject.next(newList);
    })
  }

  updateStudent(student: Student): void {
    let updatedStudent = this.studentsSubject.getValue().find(stu => stu.id === student.id);
    updatedStudent = {...updatedStudent, ...student}
    
    this.httpClient.put(`https://63cc20169b72d2a88e0893c6.mockapi.io/alumnos/${student.id}`, updatedStudent).subscribe(_ => {
      let newList = this.studentsSubject.getValue().map(stu => stu.id === updatedStudent!.id ? updatedStudent! : stu);
      this.studentsSubject.next(newList)
    })
  }

  deleteStudent(student: Student): void {
    this.httpClient.delete(`https://63cc20169b72d2a88e0893c6.mockapi.io/alumnos/${student.id}`).subscribe(_ => {
      let newList = this.studentsSubject.getValue().filter(stu => stu.id !== student.id);
      this.studentsSubject.next(newList);
    })
  }

  setModeObservable(mode: string): void {
    this.modeSubject.next(mode);
  }

  setElement(element: Student): void {
    this.element = element;
  }
}

students-list.component.ts:

export class StudentsListComponent implements OnDestroy {

  displayedColumns: string[] = ['ID', 'Alumno', 'Email', 'Editar', 'Eliminar'];

  studentsSubscription: Subscription;
  coursesSubscription: Subscription;
  modeSubscription: Subscription;

  public students: Student[];
  public courses: CourseInterface[];
  public mode: string;

  constructor(private studentService: StudentService, private courseService: CoursesService) { 
    this.studentsSubscription = this.studentService.students$.subscribe(students => {
      this.students = students;
    } );

    this.coursesSubscription = this.courseService.courses$.subscribe(courses => this.courses = courses);

    this.modeSubscription = this.studentService.mode$.subscribe(mode => {
      this.mode = mode
    })
  }


  ngOnDestroy(): void {
    this.studentsSubscription.unsubscribe();
    this.coursesSubscription.unsubscribe();
    this.modeSubscription.unsubscribe();
  }

  delete(student: Student): void {
    this.studentService.deleteStudent(student);
  }

  setMode(mode: string, element: Student) {
    this.studentService.setElement(element)
    this.studentService.setModeObservable(mode);
  }

}

EDIT

As requested, here's my HTML:

<div class=" my-5" >
  <table mat-table [dataSource]="students" class="mat-elevation-z8">
      <ng-container matColumnDef="ID">
        <th mat-header-cell *matHeaderCellDef> ID </th>
        <td mat-cell *matCellDef="let element"> {{element.id}} </td>
      </ng-container>
    
      <ng-container matColumnDef="Alumno">
        <th mat-header-cell *matHeaderCellDef> Nombre y Apellido </th>
        <td mat-cell *matCellDef="let element"> {{element | studentsNamesPipe}}</td>
      </ng-container>
    
      <ng-container matColumnDef="Email">
        <th mat-header-cell *matHeaderCellDef> Email </th>
        <td mat-cell *matCellDef="let element"> {{element.email}} </td>
      </ng-container>

      <ng-container matColumnDef="Editar">
        <th mat-header-cell *matHeaderCellDef> </th>
        <td mat-cell *matCellDef="let element">
          <button mat-icon-button color="primary" (click)="setMode('Editar', element)">
            <mat-icon >edit</mat-icon>
          </button>
        </td>
      </ng-container>

      <ng-container matColumnDef="Eliminar">
        <th mat-header-cell *matHeaderCellDef> </th>
        <td mat-cell *matCellDef="let element">
          <button mat-icon-button color="warn" (click)="delete(element)">
            <mat-icon >delete</mat-icon>
          </button>
        </td>
      </ng-container>
    
      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>
</div>

EDIT 2:

StackBlitz link with all this code: https://stackblitz.com/edit/angular-ivy-jcsebj?file=src/app/app.component.ts


Solution

  • When you add or delete an element in mat-table, you have to create a new array instance to re-render the table.

    In createStudent, newList is derived from this.studentsSubject.getValue(), which uses an existing array instance. So your table does not be rendered.

    In deleteStudent, you are creating new array instance for newList. So your table is re-rendered.

    One solution is to create a new array instance for newList.

      createStudent(student: Student): void {
               :
        let newList = [...this.studentsSubject.getValue()];
               :
      }