components: Docs bug: How to add rows to table

Bug, feature request, or proposal:

We really need a straightforward example of how to dynamically add a row to a table, I see some other issues in the issue tracker on this, but am still having trouble finding info on it, so I am getting frustrated tbh

What is the expected behavior?

Expected to find docs for dynamically adding a row

What is the current behavior?

Do not find docs to dynamically add a row

What are the steps to reproduce?

Look at docs until giving up


I didn’t post this SO question, but there are several like it: https://stackoverflow.com/questions/47581267/how-to-add-data-dynamically-to-mat-table-datasource

I assume the answer is something like this:

export class MyDataSource extends DataSource<any> {
  // implement connect, etc
}

const ds = new MyDataSource();

// ...later on, we want to add some rows

ds.whatDoWeCallToAddSomeRowsPlease(); // ???

Adding rows dynamically should be one of the first examples in the docs, I would hope, please advise

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 4
  • Comments: 31 (23 by maintainers)

Most upvoted comments

MatTableDataSource does not have the renderRows method. It’s in CdkTable | MatTable.

<mat-table #myTable [dataSource]="myDataSource"></mat-table>

class MyComponent {
    public myDataSource: MatTableDataSource<any>;
    @ViewChild('myTable') myTable: MatTable<any>;

    constructor() {
        this.myDataSource = new MatTableDataSource<any>([]);
    }

    public addRow(row: any) {
        this.myDataSource.data.push(row);
        this.myTable.renderRows();
    }
}

However, in Angular 5.2.0 it still doesn’t work for me. I don’t even see new rows (@ORESoftware at least sees empty rows).

It only works when I do:

    public addRow(row: any) {
        this.myDataSource.data.push(row);
        this.myDataSource.data = this.myDataSource.data.slice();
    }

which I find not very elegant.

@ORESoftware I’m sure @andrewseguin would be able to explain more effectively, but I’ll try:

DataSource

When using MatTable or CdkTable, you must provide it with a data source. Any data source implementation that you provide needs a connect() and disconnect() method. The former is used by the table as a consistent interface to get a stream of data to display.

The connect method should return an Observable<MyRowType[]>. There is an agreement between the data source and the view that the table only needs to perform change detection when the Observable emits, and not any more. If that agreement did not exist, the table would have to perform change detection on each every cycle, which would be highly unnecessary in most use cases.

MatTableDataSource

As I’m sure you’re aware, this is a convenience implementation of a DataSource. You provide an array of rows (via data) and it provides a very simple interface for setting up filtering/pagination/etc on those rows.

Any time you want to change the data that the data source is sending to the table, you simply overwrite the data property, and that array will propagate through any established filters, pagination, or sorting and be sent to the MatTable.

But why not just allow push and unshift?

Those operations mutate the array. Neither the data source nor the table can be aware of the changes you’ve made to your array by using those operations without liberal use of change detection.

I still really want addRow and removeRow methods

Maybe try extending MatTableDataSource?

class MyCustomDataSource extends MatTableDataSource {

  addRow(newData) {
    this.data = [...this.data, newData];
  }

  removeRow() {
    this.data = this.data.slice(0, this.data.length - 1)
  }

}

@oomz it’s like so

new MatTableDataSource().renderRows()

you normally wouldn’t use it like that, but you get the idea. note that this method is only available in 5.2.0+

@ORESoftware

this.myDataSource = new MatTableDataSource();
this.myDataSource.data = dataToRender;

// ...later on, we want to add some rows

this.myDataSource.data = differentDataToRender;

Same issues some other users are having here. The table only updates if you change the whole array, concat and push DO NOT work.

My ‘solution’: this.dataSource = this.dataSource.concat(someNewData)

Any solution for this? I’m having the same problem when I try to edit a row with the data that comes to me through a socket.

I’m using angular material 5.2.5

The renderRows() method does not seem to work here

UPDATE:

After much searching I could solve it with this example in stackblitz

Also add ChangeDetectorRef this works for me

@andrewseguin @willshowell

one thing that still perplexes me, given Will’s example above:

ngOnInit() {
  this.data.currentMessage.
    .scan((accum, message) => accum.concat(message), [])
    .subscribe(messages => this.dataSource.data = messages);
}

do you really want users overwriting the entire array?

this.dataSource.data = messages

it doesn’t make a lot of sense to overwrite the whole array. not only that, but to be required to overwrite the entire array to get re-rendering? It makes no sense to me. I don’t see how the library will save on rendering time if it has to compare all the elements of the old array with the new one to see where changes are, etc.

@ORESoftware thanks for the video.

  1. FWIW, if you’re only ever adding rows from the data service, perhaps try something like this:

    constructor(private data: ChromeDataService) {
      this.dataSource = new MatTableDataSource<ChromeMessage>([]);
    }
    
    ngOnInit() {
      this.data.currentMessage.
        .scan((accum, message) => accum.concat(message), [])
        .subscribe(messages => this.dataSource.data = messages);
    }
    

    Obviously I don’t know how your data service works or really any of the rest of the app, but maybe it will show how easy it can be to add rows.

    This way could be even simpler:

    const oldData = this.dataSource.data;
    oldData.push(newRow);
    this.dataSource.data = oldData;
    
  2. What you’re experiencing with empty rows not showing data until you navigate tabs is almost certainly a symptom of change detection not being run when you expect. You could validate this by adding a click handler to one of your cell templates. When you click it, change detection should run and the rows should update.

  3. You talk about performance costs around 3:30. As far as I’m aware, this whole system was designed to be as performant as possible, thus leaving it up to the user to define when change detection should occur. Creating a new array via concat or spread assignment doesn’t do a deep copy, AFAIK it simply updates the object reference, so I think the costs you’re expecting by assigning a new array are much much lower than anticipated.

  4. In any case, writing your own data source is pretty easy and you can make it as flexible and customizable as you need. The only condition is that your connect() method emits each time you want to render an update.

  5. Finally, consider what addRow() and removeRow() open up… Why include those but not addRows(), removeRows(), swapRows(), invertTable(), replaceRow()? They’re all valid needs, but supporting them all would be overkill when it’s so easy to accomplish otherwise.

Thanks, I don’t think that’s a great solution (overwriting the original array), I made a video demonstrating the problem

https://www.useloom.com/share/83b4388cf48f49178280bec3d3bbc984