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

10

37

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.

Chaparro answered 22/11, 2017 at 20:42 Comment(0)
C
27

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;
    }
}
....
Chaparro answered 28/11, 2017 at 21:43 Comment(3)
Great. Just to mention, you can define the croatian translations for the whole app in the app.module.ts provider section like this: { provide: MatPaginatorIntl, useClass: MatPaginatorIntlCro}. If you are interested in my implementation of MatPaginatorIntlCro, I can post it.Millenary
About translations, sure :). Recently I've figured out entire new logic for fronted table update issue so I'm gonna write sample app and put on github. ;)Chaparro
Getting this error for ur whole code while I was using:-core.js:6260 ERROR TypeError: Cannot read property 'sortChange' of undefinedGomar
M
9

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;
}
Millenary answered 26/11, 2017 at 14:25 Comment(4)
I'm actually Android dev, that's why I'm little lost here. :D Anyway refreshTable does work beacuse I can see page flickers for a second but still I have problems with: "data.push(result.vendor);". My best guess is that I should do data.push inside my dataService.ts since I get values from dialogs there. Table behaviorSubject is defined inside DataServices.ts as well.Chaparro
I'm doing this in my component, not the service. To me it seems not a business concern (service) but a display concern (component). Anyways, it's working very nicely (super fast without a new http request). If you can reproduce a plunker, maybe I can help. If it is not working, then you probably didn't update the datasource correctly.Millenary
Quick update, I've managed to do it. Now I just have to build logic for delete, edit. You code will be helpfull for that.Chaparro
Getting this error for ur whole code while I was using:-core.js:6260 ERROR TypeError: Cannot read property 'sortChange' of undefinedGomar
S
7

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/AngularMaterialCRUD

Here is the link to demo: https://crud-angular6.azurewebsites.net

Updated to Angular 8

Updated to Angular 12

Click here to see screenshot

Schiffman answered 3/9, 2018 at 20:1 Comment(1)
Welcome to StackOverflow. While the links given may answer the question, it is better to include the essential parts of the answer here and provide the links for reference. Link-only answers can become invalid if the linked page changes. See meta.stackexchange.com/q/8231Waken
J
7

Actually you don't have refresh the table after edit, if you have the following HTML:

<mat-table [dataSource]="dataSource" matSort>
      <ng-container matColumnDef="userName">
        <mat-header-cell mat-sort-header> UserName </mat-header-cell>
        <mat-cell *matCellDef="let row"> {{row.userName}} </mat-cell>
      </ng-container>
      <ng-container matColumnDef="actions">
        <mat-cell *matCellDef="let user">
          <button mat-icon-button matTooltip="Edit" (click)="editUser(user)">
            <mat-icon>edit</mat-icon>
          </button>
        </mat-cell>
      </ng-container>
      <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
      <mat-row *matRowDef="let row; columns: displayedColumns;">
      </mat-row>
</mat-table>

And, in the .ts you have:

private editUser(user?: User) {
    let userTest: User = user;
    userTest.userName = "user123";
  }

You can see that automatically the row when you push Edit the username change (in this case to "user123")

Josephus answered 5/10, 2018 at 14:18 Comment(3)
Yes this is logical but how about working with modals?Smutchy
You can pass data by referenceGrossularite
I used the suggested answer and it works and no refresh table is needed just update the content of the row after the modal is closed, thanks!!Emanuele
O
5

This solution uses my existing delete code but the same for update code. The key issue is finding the array index for the item edited or deleted. Notice that once the result is a success I call a success modal to notify the user then call a function to remove the row from the data table. Or you can update the data in that row with a little different code such as pushing the data into the array of objects. This way we don't have to download all the data again.

public deleteMember(memberId) {
      // Call the confirm dialog component
      this.confirmService.confirm('Confirm Delete', 'This action is final. Gone forever!')
          .switchMap(res => {if (res === true) {
              return this.appService.deleteItem(this.dbTable, memberId);
          }})
          .subscribe(
              result => {
                this.success();
                // Refresh DataTable to remove row.
                this.updateDataTable (memberId);
              },
              (err: HttpErrorResponse) => {
                  console.log(err.error);
                  console.log(err.message);
                this.messagesService.openDialog('Error', 'Delete did not happen.');
              }
          );
  }

Now lets remove, or update, that deleted, or edited, row.

private dsData: any;
  // Remove the deleted row from the data table. Need to remove from the downloaded data first.
  private updateDataTable (itemId) {
    this.dsData = this.dataSource.data;
    if (this.dsData.length > 0) {
      for (let i = 0; i < this.dsData.length; i++ ) {
        if (this.dsData[i].member_id === itemId) {
          this.dataSource.data.splice(i, 1);
        }
      }
    }
    this.dataSource.paginator = this.paginator;
  }
Orotund answered 26/11, 2017 at 22:43 Comment(2)
Your answer actually helped me to figure out few thing. this.dataSource.data; won't work tho. I've updated question.Chaparro
I forget to add the this.dataSource.paginator = this.paginator; Now it works. Thanks you.Stuart
B
4

My answer is in Angular 6 Material 2.

I used the splice function that takes as arguments the index of the edited row, then the number of rows to delete (in your case 1) and thirdly the new version of the edited row which will be inserted at that index:

dialogRef.afterClosed().subscribe(result => {
  if(result !== '' && result !== null) {
    const idx_editedRow = this.mattabledatasource.data.indexOf(row);
    this.mattabledatasource.data.splice(idx_editedRow, 1, result);
    loadData();
  }
});
Brenner answered 30/7, 2018 at 8:48 Comment(0)
O
3

A little different approach to deleting an item and refreshing the data table. It calls the api again but this might work for smaller datasets.

public deleteMember(memberId) {
      // Call the confirm dialog component
      this.confirmService.confirm('Confirm Delete', 'This action is final. Gone forever!')
          .switchMap(res => {if (res === true) {
              return this.appService.deleteItem(this.dbTable, memberId);
          }})
          .subscribe(
              result => {
                this.success();
                // Refresh DataTable to remove row.  This solution calls the db and is a hack.
                this.ngAfterViewInit();
              },
              (err: HttpErrorResponse) => {
                  console.log(err.error);
                  console.log(err.message);
                this.messagesService.openDialog('Error', 'Delete did not happen.');
              }
          );
  }

This gets called near the top of the component of course but included here for reference.

private dbTable = 'members';
dataSource = new MatTableDataSource();

ngAfterViewInit() {
    this.appService = new AppService(this.http);
    this.dataSource.sort = this.sort;
    this.dataSource.paginator = this.paginator;


    // Populate the Material2 DataTable.
    Observable.merge(this.paginator.page)
      .startWith(null)  // Delete this and no data is downloaded.
      .switchMap(() => {
        return this.appService.getItems( this.dbTable,
          this.paginator.pageIndex);
      })
      .map(data => {
        return data.resource;
      })
      .subscribe(data => {
        this.dataLength = data.length;
        this.dataSource.data = data;
      });
  }
Orotund answered 26/11, 2017 at 17:44 Comment(2)
Isn't your code something like mine temp solution? After I do CRUD operation I just call "getAllData" using "this.loadData();".Chaparro
Yes, the call to reload the data is the same idea. However, my code is different and could help others with another approach. I hope :-)Orotund
G
2

Can you take a look at

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);
  });
}

Is the POST request working and sending the data? You reference Baza in the POST request which should be 'baza' (lowercase B). Maybe the request is failing because of this and the observable subscription is never fulfilled... you could double check that theory with an error handler on the subscription.

addItem(baza: Baza): void {
  this.httpClient.post(this.API_URL, baza).subscribe(data => {
      const copiedData = this.data.slice();
      copiedData.push(baza);
      console.log(copiedData);
      this.dataChange.next(copiedData);
  }, (errror) => {
    console.log(error);
  });
}

Finally as for edits, my approach would differ slightly. Inject the same instance of the DataService into the component and pass this same reference to the table DataSource rather than a new instance. Next, pass the entire baza object to the edit dialog, not just its properties. Next, on dialog close, pass the original (unedited object) as well as the new properties (or better yet, a new object of Baza class with the edited fields). Send these to our data service with an "edit/update" method. The edit/update method will filter the existing set of data array looking for any entries that match our unedited object, and setting them to equal our new object. Slightly abstracted example given below

// e.g. Component

export class BazaComponent implements OnInit {
  ....
  constructor(
    public httpClient: HttpClient, 
    public dialog: MatDialog,
    public dataService: DataService
  ){}
  ....
  public loadData() {
    this.dataSource = new ExampleDataSource(this.dataService, 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;
      });
  }
  ....
  startEdit(baza: Baza) {
    const dialogRef = this.dialog.open(EditDialogComponent, {
      data: {
        baza: baza
      }
    });

    dialogRef.afterClosed().subscribe(result => {
      // result will be simple array of our 'old' baza object that we passed in, and the 'new' baza object that contains the edits
      this.dataService.updateItem(result[0], result[1]);
    });
  }

  dialogRef.close(['close',editBaza,baza]);

// e.g. service

export class DataService {
  ....
  set data(data: Baza[]) {
    this.dataChange.next(data);
  }
  ....
  updateItem(oldBaza: Baza, newBaza: Baza){
    this.data = this.data.map((baza: Baza) => {
      if(baza === oldBaza) return newBaza;
      return baza;
    });
  }
Grizelda answered 26/11, 2017 at 20:38 Comment(6)
Yes, Http part works, actually all CRUD operations work. :) Baza is just an model: import { Baza } from '../models/baza';. Nothing fancy going on there, I can edit question if you wan't to take a look on it. I only have issues with Mat Table and since everything is kinda new there's no working crud samples on the web.Chaparro
I'm looking into your answer, got error in DataService, updateItem function: ERROR in src/app/services/data.service.ts(40,10): error TS2540: Cannot assign to 'data' because it is a constant or a read-only property.Chaparro
Because we are changing a BehaviourSubject not a simple array of data, I have updated answer to include a set data method. Can you try with that added.Grizelda
TS2339:Property 'next' does not exist on type 'Baza[]'. I'm thinking you might want to use push instead: this.data.push(data);. Then I get other error: TS2345: Argument of type 'Baza[]' is not assignable to parameter of type 'Baza'. Property 'id' is missing in type 'Baza[]'. Thanks for trying to help out, I can upload entire code to plunker or something if It helps.Chaparro
Sorry, I changed the set data method once more, small typo there. Plunker would be helpful if you are still having issues.Grizelda
Still doesn't work, I've updated question, could you take a look please. Now I have to figure out how to get dialog data inside Table component. Atm it works like this. Table component opens dialog -> AddDialogComponent gets data from forms -> DataService gets data and does HttpRequest. It would be best if I could update table from there since service collects data from forms. I'm gonna make plunker later.Chaparro
D
2

Structure of the jobposting.component.ts file:

export class JobPostingComponent implements OnInit {
  values: JobPosting[];
  columns: string[] = ['title', 'vacancies','division.name'];
  displayedColumns: string[] = ['actions'].concat(this.columns);
  dataSource: MatTableDataSource<JobPosting>;

I used findIndex for the row to be updated and insert the updated values of the row to that index of the values array.

onEdit(data: JobPosting) {
  const dialogRef = this.dialog.open(AddJobPostingComponent, {
    data,
    width: '1000px'
  });

  dialogRef.afterClosed().subscribe(res => {
    if (res !== undefined) {
      const id = res.id;
      const index = this.values.findIndex(x => x.id === id);
      this.values[index] = res;
      this.dataSource.data = this.values;
    }
  });
}
Dropline answered 2/7, 2019 at 8:34 Comment(0)
F
2

If somebody using an array instead of observable as a MatDataSource, I have found a workaround that is working for me. this is my dataSource

this.dataSource = new MatTableDataSource(ingredientList);

this is my update method

updateRowData(id, newData): void {
   const index = this.dataSource.data.findIndex((inc: any) => inc.ingredientId === id);
   if (index !== -1) {
     this.dataSource.data[index] = newData;
     this.dataSource.data = this.dataSource.data.slice(0);

     this.table.renderRows();
   }
  }
Fiedling answered 30/3, 2020 at 8:16 Comment(2)
what is this.table?Pyles
Must use @ViewChild(MatTable, {static: true}) table: MatTable<>;Woof

© 2022 - 2024 — McMap. All rights reserved.