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
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()];
:
}