Angular - Material Table, is it possible to update rows without entire table refresh?
Took me some time but I finally got everything working. Your answers and different approaches helped aswell. So, here's my CRUD implementation if anyone gets in trouble with this:
https://github.com/marinantonio/angular-mat-table-crud
Screenshot:
Or you can check project demo:https://marinantonio.github.io/angular-mat-table-crud/
Key parts are in table.ts file:
....addNew(issue: Issue) { const dialogRef = this.dialog.open(AddDialogComponent, { data: {issue: issue } }); dialogRef.afterClosed().subscribe(result => { if (result === 1) { this.exampleDatabase.dataChange.value.push(this.dataService.getDialogData()); this.refreshTable(); } }); } startEdit(i: number, id: number, title: string, state: string, url: string, created_at: string, updated_at: string) { this.index = i; this.id2 = id; console.log(this.index); const dialogRef = this.dialog.open(EditDialogComponent, { data: {id: id, title: title, state: state, url: url, created_at: created_at, updated_at: updated_at} }); dialogRef.afterClosed().subscribe(result => { if (result === 1) { // Part where we do frontend update, first you need to find record using id const foundIndex = this.exampleDatabase.dataChange.value.findIndex(x => x.id === this.id2); // Then you update that record using dialogData this.exampleDatabase.dataChange.value[foundIndex] = this.dataService.getDialogData(); // And lastly refresh table this.refreshTable(); } }); } deleteItem(i: number, id: number, title: string, state: string, url: string) { this.index = i; this.id2 = id; const dialogRef = this.dialog.open(DeleteDialogComponent, { data: {id: id, title: title, state: state, url: url} }); dialogRef.afterClosed().subscribe(result => { if (result === 1) { const foundIndex = this.exampleDatabase.dataChange.value.findIndex(x => x.id === this.id2); this.exampleDatabase.dataChange.value.splice(foundIndex, 1); this.refreshTable(); } }); } private refreshTable() { // If there's no data in filter we do update using pagination, next page or previous page if (this.dataSource._filterChange.getValue() === '') { if (this.dataSource._paginator.pageIndex === 0) { this.dataSource._paginator.nextPage(); this.dataSource._paginator.previousPage(); } else { this.dataSource._paginator.previousPage(); this.dataSource._paginator.nextPage(); } // If there's something in filter, we reset it to 0 and then put back old value } else { this.dataSource.filter = ''; this.dataSource.filter = this.filter.nativeElement.value; }}....
As I see from your code that you are using pagination, you can do the following after the crud operation:
this.dataSource.paginator = this.paginator;
This will refresh the current page. And, glad someone from Croatia is using angular material.
Here's the important part from my code:
dialogRef.afterClosed().subscribe(result => { if (result === null) { return; } switch (mode) { // add new case 'C': { data.push(result.vendor); this.refreshTable(); break; } case 'U': { // update const index = data.findIndex((item) => item.buFmisVendorId === result.vendor.buFmisVendorId); if (index > -1) { data[index] = vendor; this.refreshTable(); } break; } }});private refreshTable() { this.dataSource.paginator = this.paginator;}
I have some workaround in editing data in the table without using modal windows.
You can take a look at my CRUD implementation with Angular 6 and Material
Data service
import {Injectable} from '@angular/core';import {HttpClient, HttpParams, HttpHeaders} from '@angular/common/http';import {User} from './user';@Injectable()export class UserService{private url = "http://localhost:51120";constructor(private http: HttpClient){ }getUsers(){ let getUrl = this.url + "/api/all/"; return this.http.get(getUrl);}createUser(user: User){ let saveUrl = this.url + "/api/Users"; return this.http.post(saveUrl, user); }updateUser(id: number, user: User) { const urlParams = new HttpParams().set("id", id.toString()); return this.http.post(this.url + "/api/update", user);}deleteUser(id: number){ const urlParams = new HttpParams().set("id", id.toString()); return this.http.delete(this.url + "/api/delete/" + id); }}
Component
@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css'],providers: [UserService]})export class AppComponent implements OnInit {@ViewChild(MatPaginator) paginator: MatPaginator;addNewUser: User[] = [ { Id: 0, Name: null, Age: null, Email: null, Surname: null }];users: Array<User>;showTable: boolean;statusMessage: string;isLoaded: boolean = true;displayedColumnsUsers: string[] = ['Id', 'Name', 'Surname', 'Age', 'Email', 'Change', 'Delete'];displayedColumnsAddUser: string[] = ['Name', 'Surname', 'Age', 'Email', 'Save', 'Cancel'];dataSourceUsers: any;dataSourceAddUser: any;newUser : User;constructor(private serv: UserService, public dialog: MatDialog, public snackBar: MatSnackBar) { this.users = new Array<User>();}@ViewChild(MatSort) sort: MatSort;ngOnInit() { this.loadUsers(); this.dataSourceAddUser = new MatTableDataSource();}applyFilter(filterValue: string) { this.dataSourceUsers.filter = filterValue.trim().toLowerCase(); if (this.dataSourceUsers.paginator) { this.dataSourceUsers.paginator.firstPage(); }}private loadUsers() { this.isLoaded = true; this.serv.getUsers().subscribe((data: User[]) => { this.users = data; this.users.sort(function (obj1, obj2) { // Descending: first id less than the previous return obj2.Id - obj1.Id; }); this.isLoaded = false; this.dataSourceUsers = new MatTableDataSource(this.users); this.dataSourceAddUser = new MatTableDataSource(this.addNewUser); this.dataSourceUsers.sort = this.sort; this.dataSourceUsers.paginator = this.paginator; }, error => { alert("Error: " + error.name); this.isLoaded = false; } );}deleteUserForDialog(user: User) { this.serv.deleteUser(user.Id).subscribe(data => { this.statusMessage = 'User ' + user.Name + ' is deleted', this.openSnackBar(this.statusMessage, "Success"); this.loadUsers(); })}editUser(user: User) { this.serv.updateUser(user.Id, user).subscribe(data => { this.statusMessage = 'User ' + user.Name + ' is updated', this.openSnackBar(this.statusMessage, "Success"); this.loadUsers(); }, error => { this.openSnackBar(error.statusText, "Error"); } );}saveUser(user: User) { if (user.Age != null && user.Name != null && user.Name != "" && user.Age != 0) { this.serv.createUser(user).subscribe(data => { this.statusMessage = 'User ' + user.Name + ' is added', this.showTable = false; this.openSnackBar(this.statusMessage, "Success"); this.loadUsers(); }, error => { this.showTable = false; this.openSnackBar(error.statusText, "Error"); } ); } else { this.openSnackBar("Please enter correct data", "Error") }}show() { this.showTable = true; this.addNewUser = [{ Id: 0, Name: null, Age: null, Email: null, Surname: null }];}cancel() { this.showTable = false;}//snackBaropenSnackBar(message: string, action: string) { this.snackBar.open(message, action, { duration: 3000, });}//material dialogopenDialog(element): void { const dialogRef = this.dialog.open(DialogOverviewExampleDialogComponent, { width: '250px', data: element, }); dialogRef.afterClosed().subscribe(result => { console.log('The dialog was closed'); if (result == "Confirm") { this.deleteUserForDialog(element); } });}// Form field with error messages name = new FormControl('', [Validators.required]);getErrorMessage() { return this.name.hasError('required') ? 'You must enter a value' : this.name.hasError('name') ? 'Not a valid name' : '';}age = new FormControl('', [Validators.required]);email = new FormControl('', [Validators.required, Validators.email]);surnameFormControl= new FormControl('', [Validators.required]);emailGetErrorMessage() { return this.email.hasError('required') ? 'You must enter a value' : this.email.hasError('email') ? 'Not a valid email' : '';}onSubmit(newUser:User){ this.newUser = new User(0,"",0,"","");}}
https://github.com/AleksandrChuikov/AngularMaterialCRUD
Here is the link to demo:https://crud-angular6.azurewebsites.net
Updated to Angular 8
Updated to Angular 12