Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular - Material Table, is it possible to update rows without entire table refresh?

After few weeks of Googling and only one Stackoverflown question so far I finally managed to build up my Angular CRUD App using Material Table Component. It shows data from backend (JSON) and for CRUD operations I'm using dialogs like one shown on picture (this is edit, sorry for Croatian). Dialogs might not be the best way to go, inline editing might be better. But still, for adding new item you need something like dialog.

enter image description here

Last thing I'm stuck with is how to update fields in table accordingly. So when you press 'Save' on dialog, data gets updated in backend (in MySQL table) but not in fronted. For the time being I have ugly workaround for this, every time when you do an update, it refreshes whole table as well.

Anyway here's code:

Table component:

export class BazaComponent implements OnInit {   ....   constructor(public httpClient: HttpClient, public dialog: MatDialog) {   }    ngOnInit() {     this.loadData();   }    // TODO: Simplfy this...   addNew(ident: number, naziv: string, mt: number, kutija: number,          komada: number, jm: string, orginal: number, lokacija: number, napomena: string) {     console.log('add new clicked');     const dialogRef = this.dialog.open(AddDialogComponent, {       data: {ident: ident, naziv: naziv, mt: mt, kutija: kutija,         komada: komada, jm: jm, orginal: orginal, lokacija: lokacija, napomena: napomena }     });      dialogRef.afterClosed().subscribe(result => {       console.log(result);       if (result === 1) {         this.loadData();  // --> This is a temp workaround, every time when I do CRUD operation just redraw whole thing again       }     });   }    startEdit(id: number, ident: number, naziv: string, mt: number, kutija: number,             komada: number, jm: string, orginal: number, lokacija: number, napomena: string) {      const dialogRef = this.dialog.open(EditDialogComponent, {       data: {id: id, ident: ident, naziv: naziv, mt: mt, kutija: kutija,         komada: komada, jm: jm, orginal: orginal, lokacija: lokacija, napomena: napomena}     });      dialogRef.afterClosed().subscribe(result => {       if (result === 1) {         this.loadData(); // --> This is a temp workaround, every time when I do CRUD operation just redraw whole thing again       }     });   }    deleteItem(id: number, ident: number, naziv: string, mt: number) {     const dialogRef = this.dialog.open(DeleteDialogComponent, {       data: {id: id, ident: ident, naziv: naziv, mt: mt}     });      dialogRef.afterClosed().subscribe(result => {       if (result === 1) {         this.loadData();       }     });   }     public loadData() {     this.exampleDatabase = new DataService(this.httpClient);     this.dataSource = new ExampleDataSource(this.exampleDatabase, this.paginator, this.sort);     Observable.fromEvent(this.filter.nativeElement, 'keyup')       .debounceTime(150)       .distinctUntilChanged()       .subscribe(() => {         if (!this.dataSource) {           return;         }         this.dataSource.filter = this.filter.nativeElement.value;       });   } }   export class ExampleDataSource extends DataSource<Baza> {   _filterChange = new BehaviorSubject('');    get filter(): string {     return this._filterChange.value;   }    set filter(filter: string) {     this._filterChange.next(filter);   }    filteredData: Baza[] = [];   renderedData: Baza[] = [];    constructor(private _exampleDatabase: DataService,               private _paginator: MatPaginator,               private _sort: MatSort) {     super();     // Reset to the first page when the user changes the filter.     this._filterChange.subscribe(() => this._paginator.pageIndex = 0);   }    /** Connect function called by the table to retrieve one stream containing the data to render. */   connect(): Observable<Baza[]> {     // Listen for any changes in the base data, sorting, filtering, or pagination     const displayDataChanges = [       this._exampleDatabase.dataChange,       this._sort.sortChange,       this._filterChange,       this._paginator.page,     ];      this._exampleDatabase.getAllItems();      return Observable.merge(...displayDataChanges).map(() => {       // Filter data       this.filteredData = this._exampleDatabase.data.slice().filter((item: Baza) => {         const searchStr = (item.ident + item.naziv + item.mt + item.lokacija + item.napomena).toLowerCase();         return searchStr.indexOf(this.filter.toLowerCase()) !== -1;       });        // Sort filtered data       const sortedData = this.sortData(this.filteredData.slice());        // Grab the page's slice of the filtered sorted data.       const startIndex = this._paginator.pageIndex * this._paginator.pageSize;       this.renderedData = sortedData.splice(startIndex, this._paginator.pageSize);       return this.renderedData;     });   }    disconnect() {   }    /** Returns a sorted copy of the database data. */   sortData(data: Baza[]): Baza[] {   ... sort stuff } 

Here's DataService where I guess I should do field updates:

import { Injectable } from '@angular/core'; import { HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http'; import { Baza } from '../models/kanban.baza'; import { BehaviorSubject } from 'rxjs/BehaviorSubject';      @Injectable()     export class DataService {       private readonly API_URL = 'http://localhost/api/'        /** Stream that emits whenever the data has been modified. */       dataChange: BehaviorSubject<Baza[]> = new BehaviorSubject<Baza[]>([]);        constructor(private httpClient: HttpClient) {       }        get data(): Baza[] {         return this.dataChange.value;       }        getAllItems(): void {         this.httpClient.get<Baza[]>(this.API_URL).subscribe(data => {           this.dataChange.next(data['items']);         });       }      addItem(baza: Baza): void {       this.httpClient.post(this.API_URL, Baza).subscribe(data => {           //THIS WAS MY BEST TRY BUT IT DOESN'T WORK :(           const copiedData = this.data.slice();           copiedData.push(baza);           console.log(copiedData);           this.dataChange.next(copiedData);       });     }         updateItem(baza: Baza): void {         this.httpClient.put(this.API_URL + baza.id, baza).subscribe();       }        deleteItem(id: number): void {         this.httpClient.delete(this.API_URL + id, {headers: new HttpHeaders().set('Access-Control-Allow-Origin', '*')} ).subscribe();     } } 

UPDATE 27.11.2017:

Okay, I've finally figured out how to trigger new row addition. I had to call dataChange.value inside table component. Once you load it with some data new row will appear instantaniously.

const data = {id: 208, ident: 233, naziv: 'test', mt: 291, komada: 2, jm: 'a', orginal: 100, lokacija: 3, napomena: 'pls work'}; this.exampleDatabase.dataChange.value.push(data); 

Same thing in DataService won't work:

this.dataChange.value.push(data);  

Plunker is here:

https://plnkr.co/edit/IWCVsBRl54F7ylGNIJJ3?p=info

EDIT 28.11.2017:

Now only thing left is building logic for add, edit and delete. For add is easy, it's just `value.push(data)'. Thanks for help everyone.

like image 551
besthiroeu Avatar asked Nov 22 '17 20:11

besthiroeu


2 Answers

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;     } } .... 
like image 71
besthiroeu Avatar answered Sep 25 '22 08:09

besthiroeu


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; } 
like image 24
zszep Avatar answered Sep 22 '22 08:09

zszep