I have an Angular Material data table with CRUD operations as buttons on each row. Unfortunately the buttons aren't working. If I call the toggleActiveCourse or deleteCourse functions in ngOnInit, the data is updated as expected, but if I call them from the button in the component.html file, nothing happens. I can't figure it out. I've been fighting with it for hours. I have written many CRUD tables this way, never had this problem.
courses.component.ts
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Course } from 'src/app/core/models/course';
import { CoursesService } from 'src/app/core/services/courses/courses.service';
import { CoursesTableService } from 'src/app/core/tables/courses-table.service';
import { CourseFormComponent } from '../course-form/course-form.component';
@Component({
selector: 'app-courses',
templateUrl: './courses.component.html',
styleUrls: ['./courses.component.css']
})
export class CoursesComponent implements OnInit, OnDestroy {
tableSub: any;
showActive: boolean = true;
displayedColumns = ['name', 'section', 'guaranteedViableCurriculum', 'numberOfStudents', 'actions'];
dataSource = new MatTableDataSource<Course>();
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
constructor(
private coursesTable: CoursesTableService,
private coursesService: CoursesService,
private dialog: MatDialog,
) { }
ngOnInit(): void {
this.tableSub = this.coursesTable.coursesTable$
.subscribe(courses => {
this.dataSource.data = courses;
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
})
}
ngOnDestroy(): void {
this.tableSub.unsubscribe();
}
openFormDialog(course?: Course) {
let dialogRef = this.dialog.open(CourseFormComponent, {
data: {
course: course,
}
})
}
toggleActive() {
this.coursesTable.showActiveToggle(this.showActive);
}
doFilter(filterValue: any) {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
toggleArchiveCourse(course: Partial<Course>) {
let id = course.id;
let active = !course.active;
this.coursesService.updateCourse({id, active}).subscribe();
}
deleteCourse(course: Course) {
let result = confirm("Are you sure you want to delete '" + course.name + " | " + course.section + "'?");
if (result) this.coursesService.deleteCourse(course).subscribe();
}
}
courses.service.ts
import { Injectable } from '@angular/core';
import { AngularFireDatabase } from '@angular/fire/compat/database';
import { concatMap, from, map, Observable } from 'rxjs';
import { Course } from '../../models/course';
import { AuthService } from '../auth/auth.service';
@Injectable({
providedIn: 'root'
})
export class CoursesService {
// Observable of all courses under a user
courses$: Observable<Course[]> = this.auth.user$
.pipe(concatMap(user => {
return this.db.list(`${user.uid}/courses`,
ref => ref.orderByChild('section'))
.snapshotChanges()
.pipe(map(docArray => {
return docArray.map(doc => {
let c = doc.payload.val() as Course;
let id = doc.payload.key as string;
let course = {
id: id,
...c,
};
return course;
})
}))
}));
constructor(
private db: AngularFireDatabase,
private auth: AuthService
) { }
// Create new course
createCourse(course: Course): Observable<any> {
return this.auth.user$
.pipe(concatMap(user => {
let coursesRef = this.db.list(`${user.uid}/courses`);
return from(coursesRef.push(course));
}))
}
// Update course
updateCourse(course: Partial<Course>): Observable<any> {
return this.auth.user$
.pipe(concatMap(user => {
let courseRef = this.db.object(`${user.uid}/courses/${course.id}`);
delete course.id;
return from(courseRef.update(course));
}))
}
// Delete course
deleteCourse(course: Course): Observable<any> {
return this.auth.user$
.pipe(concatMap(user => {
let courseRef = this.db.object(`${user.uid}/courses/${course.id}`);
return from(courseRef.remove());
}))
}
}
courses.component.html
<div class="container">
<h1>Courses</h1>
<div class="table-tools" fxLayout="row" fxLayoutAlign="space-between center">
<mat-form-field fxFlex="40%">
<mat-label>Filter Courses</mat-label>
<input matInput (keyup)="doFilter($any($event.target).value)">
</mat-form-field>
<div class="card-buttons">
<div class="buttons" fxFlex fxLayout="row" fxLayoutGap="10px">
<button mat-mini-fab color="primary" matTooltip="Add a New Course" (click)="openFormDialog()">
<mat-icon>add</mat-icon>
</button>
<button mat-mini-fab color="accent" matTooltip="Import Courses from Canvas">
<mat-icon>import_export</mat-icon>
</button>
</div>
</div>
</div>
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
Course Name
</th>
<td mat-cell *matCellDef="let course">
{{ course.name }}
</td>
</ng-container>
<ng-container matColumnDef="section">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
Course Section
</th>
<td mat-cell *matCellDef="let course">
{{ course.section }}
</td>
</ng-container>
<ng-container matColumnDef="guaranteedViableCurriculum">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
Guaranteed Viable Curriculum
</th>
<td mat-cell *matCellDef="let course">
{{ course.guaranteedViableCurriculum }}
</td>
</ng-container>
<ng-container matColumnDef="numberOfStudents">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
Number of Students
</th>
<td mat-cell *matCellDef="let course">
{{ course.numberOfStudents }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>
Actions
</th>
<td mat-cell *matCellDef="let course">
<button mat-icon-button color="primary" #tooltip="matTooltip" matTooltip="Edit {{ course.name }} | {{ course.section }}" (click)="openFormDialog(course)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="accent" #tooltip="matTooltip" matTooltip="Archive {{ course.name }} | {{ course.section }}" (click)="toggleArchiveCourse(course)">
<mat-icon>archive</mat-icon>
</button>
<button mat-icon-button color="warn" #tooltip="matTooltip" matTooltip="Delete {{ course.name }} | {{ course.section }}" (click)="deleteCourse(course)">
<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 class="table-footer" fxLayout="row" fxLayoutAlign="space-between center">
<mat-checkbox class="checkbox" labelPosition="after" [(ngModel)]="showActive" (change)="toggleActive()">Show only active courses</mat-checkbox>
<mat-paginator [pageSizeOptions]="[5, 10, 15, 20]" aria-label="Select page"></mat-paginator>
</div>
</div>