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;
}
//snackBar
openSnackBar(message: string, action: string) {
this.snackBar.open(message, action, {
duration: 3000,
});
}
//material dialog
openDialog(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/Angular6MaterialCRUD
Here is the link to demo: https://crud-angular6.azurewebsites.net
Updated to Angular 8