title: Angular - Material Table, is it possible to update rows without entire table refresh?
body: <p>After few weeks of Googling and only one Stackoverflown question so far I finally managed to build up my <strong>Angular CRUD App</strong> using <strong>Material Table Component</strong>. 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.</p>
  
  <p><a href="https://i.stack.imgur.com/TvaYF.png" rel="noreferrer"><img src="https://i.stack.imgur.com/TvaYF.png" alt="enter image description here"></a></p>
  
  <p>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.</p>
  
  <p>Anyway here's code:</p>
  
  <p>Table component:</p>
  
  <pre><code>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 =&gt; {
        console.log(result);
        if (result === 1) {
          this.loadData();  // --&gt; 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 =&gt; {
        if (result === 1) {
          this.loadData(); // --&gt; 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 =&gt; {
        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(() =&gt; {
          if (!this.dataSource) {
            return;
          }
          this.dataSource.filter = this.filter.nativeElement.value;
        });
    }
  }
  
  
  export class ExampleDataSource extends DataSource&lt;Baza&gt; {
    _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(() =&gt; this._paginator.pageIndex = 0);
    }
  
    /** Connect function called by the table to retrieve one stream containing the data to render. */
    connect(): Observable&lt;Baza[]&gt; {
      // 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(() =&gt; {
        // Filter data
        this.filteredData = this._exampleDatabase.data.slice().filter((item: Baza) =&gt; {
          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
  }
  </code></pre>
  
  <p>Here's DataService where I guess I should do field updates:</p>
  
  <pre><code>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&lt;Baza[]&gt; = new BehaviorSubject&lt;Baza[]&gt;([]);
  
        constructor(private httpClient: HttpClient) {
        }
  
        get data(): Baza[] {
          return this.dataChange.value;
        }
  
        getAllItems(): void {
          this.httpClient.get&lt;Baza[]&gt;(this.API_URL).subscribe(data =&gt; {
            this.dataChange.next(data['items']);
          });
        }
  
      addItem(baza: Baza): void {
        this.httpClient.post(this.API_URL, Baza).subscribe(data =&gt; {
            //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();
      }
  }
  </code></pre>
  
  <p><strong>UPDATE 27.11.2017:</strong></p>
  
  <p>Okay, I've finally figured out how to trigger new row addition. I had to call <code>dataChange.value</code> inside table component. Once you load it with some data new row will appear instantaniously.</p>
  
  <pre><code>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);
  </code></pre>
  
  <p>Same thing in DataService won't work:</p>
  
  <pre><code>this.dataChange.value.push(data); 
  </code></pre>
  
  <p>Plunker is here:</p>
  
  <p><a href="https://plnkr.co/edit/IWCVsBRl54F7ylGNIJJ3?p=info" rel="noreferrer">https://plnkr.co/edit/IWCVsBRl54F7ylGNIJJ3?p=info</a></p>
  
  <p><strong>EDIT 28.11.2017:</strong></p>
  
  <p>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.</p>