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: Alt Text

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

Click here to see screenshot