Components: Docs bug: How to add rows to table

Created on 8 Jan 2018  路  31Comments  路  Source: angular/components

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

Most helpful comment

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.

All 31 comments

But the problem with my code above, is that DataSource is not exported from @angular/materialanymore, so that seems to have changed...I am also extremely confused why this both compiles and runs without errors:

  this.dataSource = new MatTableDataSource();
  this.dataSource.connect()

connect to what? is the question

@ORESoftware Actually, the documentation site uses the Markdown docs (such as card.md, button.md, etc.) and transforms them to HTML files. The readme for the library in each sub-library then redirects you to the documentation.

@ORESoftware, isn't the same info in https://material.angular.io/components/table/overview?

@ORESoftware

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

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

this.myDataSource.data = differentDataToRender;

@willshowell so in your example you just want users to overwrite the entire variable? seems like

this.myDataSource.data.push() / this.myDataSource.data.unshift() would be a better idea?

the problem is that when I push to the existing array, the table doesn't re-render... I can try re-assigning to a new array, but that seems to just be a band-aid.

@julianobrasil I have looked at that overview 10 times, it does not make it clear how to add rows.

There should be a method on MatTableDataSource that allows you to add rows etc.

const ds = new MatTableDataSource(x);
ds.addRow({});
ds.removeRow();

re-rendering everything by just overwriting the entire array seems crazy to me

In fact, there should be a way to trigger MatDatatable update. I'll take closer look later to see whether I can find out something.

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

}

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

@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.

Ok, so what you are saying is that in the Material codebase, you have something like:

Object.defineProperty(dataSource, 'data', {
     set: function(){
         // do some majik?
     }
});

yes if that's the case, the user can modify the data array at will, and use Array.prototype methods to change what's in the table. The reason those methods might exist (raison d'etre) would be so that all the users in the world don't have to reimplement the same logic. For example, swapRows() might be tricky..idk.

Ok so the solution given does not work. I have this code:

typescript const a = self.dataSource.data.slice(0); a.push(m as any); self.dataSource.data = a; self.dataSource.filter = '';

blank rows _are_ added... But I have to recreate the entire component to get the table cells to render. So what's in the video happens, no matter what I do. Something is wrong.

@ORESoftware source is here:

https://github.com/angular/material2/blob/af44b9dad774b91eac61f748c76177939587e4f4/src/lib/table/table-data-source.ts#L51-L53

That does sound like something is wrong. Are you able to recreate the issue in a stackblitz?

@ORESoftware I've tried to build a reproduction that's similar to your setup,

https://stackblitz.com/edit/angular-material2-issue-gd6jr5?file=app/table.component.ts

Can you see if you can get identify the difference in your app or fork the stackblitz and reproduce it there?

@willshowell thanks, I will get to this tomorrow or Saturday, will try to reproduce with stackblitz

Closing since the discussion mostly revolves around troubleshooting rather than an issue. Feel free to continue the discussion here.

Note that the data source input will soon open up to arrays and streams directly, as well as exposing the API for forcing the table to render row changes.

@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.

It _is_ working if I simply overwrite the array, but it seems restrictive, un-performant, and a poor user experience, because as a user I would never expect to have to do that.

it's fine if you do not want to provide users with an immense number of methods to add rows / remove rows, etc. users can implement that themselves. but what users really need is some method on MatTableDataSource that can tell it that changes have occurred.

something like this:

addRowToTable(r: Row){
  this.dataSource.data.push(r);
  this.dataSource.update();   // tell the table re-render (does something like this exist?)
}

@ORESoftware

poor user experience

The table now has a renderRows() method per https://github.com/angular/material2/pull/9489 (in 5.2.0). Hopefully that helps.

@willshowell thanks I will try that

I am getting:

this.dataSource.renderRows is not a function, maybe I need to install the latest version of material? Looks like I need to install 5.2.0. Nvm, duh.

@ORESoftware how did you use renderRows(), where should i call this function?

@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+

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.

@marcj yes it's neither elegant nor performant...supposedly the latest version of Material2 has a renderRows() method for MatTableDataSource, can you try the latest version?

This firstime I use Angular Material and still learn for build apps but I'm stuck in this problem. user that familiar with 'datasource' (like on other desktop programming) will think that it will have function like 'addRow', 'clear', 'deleteRow' etc.
So I think it will very usefull if mat-table / cdk-table has ability to do that, at least an access to directive/component to append/modify row.

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

I am using angular6.0, and it still can't update automatically unless overwrite the hole array. It's crazy.

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)

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.

thanks dude thas work for me

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings