angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.32k stars 6.73k forks source link

[Table] Add example with dynamic data (add/remove/edit rows) #5917

Closed tyler2cr closed 3 years ago

tyler2cr commented 7 years ago

Bug, feature request, or proposal:

cdk table API docs need instructions and/or an example of updating the dataSource in the table connecting the table to a data source

What is the expected behavior?

seeing implementation instructions and/or an example of how to send the updated dataSource to the table so that the connect function is called

What is the current behavior?

The docs only cover rendering data on initial load of the component, but if a service updates the data, there isn't a documented way to update the data-table

What are the steps to reproduce?

Providing a Plunker (or similar) is the best way to get the team to see your issue. Plunker template: https://goo.gl/DlHd6U

What is the use-case or motivation for changing an existing behavior?

the md-table API doc hints at an approach but also lacks clarity

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

angular 4 material 2 typescript 2.4 windows

Is there anything else we should know?

andrewseguin commented 7 years ago

Sounds good to me, I'll make an example that includes adding/removing/editing rows.

Lakston commented 7 years ago

I hope I can add to this request without creating a new Issue, I've been looking at the example code provided in both the angular material docs and the cdk guide and I'm struggling to understand how to just send my data (a simple array of objects) to the table. Do I need my data to be a BehaviorSubject ? Do I need the typescript get too ?

The example generates data with loops and is quite confusing to me.

A simpler example with just a small simple array would really help ! I'll create a new Issue if needed.

irowbin commented 7 years ago

hello @andrewseguin It would be nice if you add collapsible row too.

ilmars commented 7 years ago

Hi @andrewseguin, alos would be nice to have example with server data, to get server data is OK, but how to triger on new http.get request on paging, sorting and filtering? (i'm stuck with that).

willshowell commented 7 years ago

@Lakston IMO your request deserves a separate issue. While the ExampleDatabase is great as a supplier of 100 rows of random data, it convolutes the Basic Example, and I think the docs could benefit from an even simpler example with maybe a single emission of a hardcoded array of data with Observable.of.

@ilmars luckily an example with an HTTP request just got merged yesterday and will be available on the docs site soon https://github.com/angular/material2/pull/5766

tyler2cr commented 7 years ago

@Lakston what I did to accomplish that was to setup a service to provide the data array to the ExampleDatabase and then looped over each item in the array and fed them into the addUser method from the example, after changing it to match my object. So you can just replace what is being iterated over in the for loop from the example by creating a service to call for that data

tyler2cr commented 7 years ago

for anyone interested in an unpublished example:

https://github.com/angular/material2/pull/5093/files

https://github.com/angular/material2/tree/master/src/demo-app/table

tyler2cr commented 7 years ago

Some help on this would be greatly appreciated :)

https://stackoverflow.com/questions/45382599/angular-2-table-invoke-connect-function

etovian commented 7 years ago

Agreed. I wept for joy when this component arrived. But after about six hours of fiddling with this thing, I still can't get it to work with a meaningful, real-world example. I expected to just point it at an array, and I'd be off and running. But having to create a datasource and a database is especially cumbersome. If the grid is supposed to be truly unopinionated, imo it shouldn't be coupled to observables. I should be able to just point it at an array of data and an array of column definitions (like most other analogous components).

willshowell commented 7 years ago

@etovian You don't need to create a database. That's just used in the example because the example needs to generate a lot of random data to display.

You barely even have to use Observables. They're used in the examples because they massively simplify manipulating the data in response to user events and are generally very powerful and flexible. If you just want to render an array once, you can use Observable.of(myArrayOfValues) and you don't have to touch rxjs again. If you want to render it more than once,


// component
_dataSubject = new BehaviorSubject<any[]>([])

constructor() {
  this.dataSource = new MyDataSource(this._dataSubject)
}

updateValues(myArray: any[]) {
  this._dataSubject.next(myArray)
}

// MyDataSource
constructor(private _data: BehaviorSubject<any[]>) { }

connect() {
  return this._data.asObservable()
}
etovian commented 7 years ago

@willshowell Thanks for the tip! I sort of got this working:

export class MyDataSource extends DataSource<any[]> {

  data = [
    { firstName: 'Mike' },
    { firstName: 'Amy' },
    { firstName: 'Jillian' },
    { firstName: 'Juliette' }
  ];

  constructor() {
    super();
  }

  connect (): Observable<any[]> {
    return Observable.of(this.data);
  }

  disconnect (  ): void {

  }

}

The only problem is that the data doesn't load when the component using the dataSource initializes. Oddly, if I click elsewhere on the page (not even inside the component), then table displays the data. Here's all of the code:

import { Component, OnInit, ViewChild } from '@angular/core';
import { CdkTable, DataSource } from '@angular/cdk';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/observable/fromEvent';
import { InvitationService } from "../services/invitation.service";

@Component ( {
  selector: 'app-sandbox',
  templateUrl: './sandbox.component.html',
  styleUrls: [ './sandbox.component.css' ]
} )
export class SandboxComponent implements OnInit {

  dataSource: MyDataSource | null;

  displayedColumns = ['firstName'];

  @ViewChild('table')
  table: CdkTable<any>;

  constructor(private invitationService: InvitationService) {

  }

  ngOnInit() {
    this.dataSource = new MyDataSource();
    //this.dataSource.connect();
  }

}

export class MyDataSource extends DataSource<any[]> {

  data = [
    { firstName: 'Mike' },
    { firstName: 'Amy' },
    { firstName: 'Jillian' },
    { firstName: 'Juliette' }
  ];

  constructor() {
    super();
  }

  connect (): Observable<any[]> {
    return Observable.of(this.data);
  }

  disconnect (  ): void {

  }

}

And the template:

<md-table
  #table
  [dataSource]="dataSource">

  <ng-container cdkColumnDef="firstName">
    <md-header-cell *cdkHeaderCellDef> First Name </md-header-cell>
    <md-cell *cdkCellDef="let row"> {{row.firstName}} </md-cell>
  </ng-container>

  <md-header-row *cdkHeaderRowDef="displayedColumns"></md-header-row>
  <md-row *cdkRowDef="let row; columns: displayedColumns;"></md-row>

</md-table>
etovian commented 7 years ago

@willshowell Oddly, this is working exactly as expected:

import { Component, OnInit } from '@angular/core';
import { DataSource } from '@angular/cdk';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/observable/fromEvent';
import { InvitationService } from "../services/invitation.service";
import { BehaviorSubject } from "rxjs/BehaviorSubject";

@Component ( {
  selector: 'app-sandbox',
  templateUrl: './sandbox.component.html',
  styleUrls: [ './sandbox.component.css' ]
} )
export class SandboxComponent implements OnInit {

  dataSource: MyDataSource | null;

  dataSubject = new BehaviorSubject<any[]>([]);

  displayedColumns = ['firstName'];

  constructor(private invitationService: InvitationService) {
    this.invitationService.getInvitations().subscribe({
      next: value => this.dataSubject.next(value)
    });
  }

  ngOnInit() {
    this.dataSource = new MyDataSource(this.dataSubject);
  }

}

export class MyDataSource extends DataSource<any[]> {

  constructor(private subject: BehaviorSubject<any[]>) {
    super ();
  }

  connect (): Observable<any[]> {
    return this.subject.asObservable();
  }

  disconnect (  ): void {

  }

}

Since this much better represents how I'd do things in production, I'm not necessarily concerned with why the code from my previous post isn't working. Thanks again for your help! You definitely saved me some continued hair-pulling.

I can certainly get by with this, but I think that the option to work directly with an array would help folks get up and running much more quickly, even if the DataSource is a more performant solution.

Thanks to all the devs for their hard work!

willshowell commented 7 years ago

@etovian your first issue looks like https://github.com/angular/material2/issues/6199 which is resolved and will be a part of the next release.

Also an array-based data source that is plug-and-play is in the works https://github.com/angular/material2/pull/6182, though it's not clear which direction that PR will take

ctilley83 commented 7 years ago

@willshowell What does your getInvitations() method look like in your InvitationService service?

etovian commented 7 years ago
constructor ( private http: Http ) { }

getInvitations (): Observable<Invitation[]> {
    return this.http.get ( this.invitationUrl ).map ( response => response.json () );
  }
munjalpandya commented 6 years ago

I am not getting any error but the data from web API is not rendering in datatable. Only columns are displayed.

Below is my code:

` import { Component, OnInit, ViewChild } from '@angular/core'; import { UserService } from '../Service/user.service'; import { IUser } from '../Model/user'; import { DBOperation } from '../Shared/enum'; import { Observable } from 'rxjs/Rx'; import { Global } from '../Shared/global'; import { ManageUser } from './manageuser.component'; import { MdDialog, MdDialogRef } from '@angular/material'; import { UserFilterPipe } from '../filter/user.pipe'; import { DataSource } from "@angular/cdk/collections";

@Component({ templateUrl: 'app/Components/userlist.component.html' })

export class UserListComponent implements OnInit {

users: IUser[];
user: IUser;
dataSource: userDataSource;
displayedColumns = ['FirstName', 'LastName'];

constructor(private _userService: UserService) { }

ngOnInit(): void {
    this.LoadUsers();
}
LoadUsers(): void {
    this._userService.get(Global.BASE_USER_ENDPOINT)
        .subscribe(users => { this.users = users }
        );
    this.dataSource = new userDataSource(this.users);
}

}

export class userDataSource extends DataSource{

constructor(private _users: IUser[]) {
    super();
}

connect(): Observable<IUser[]> {
    return Observable.of(this._users);
}

disconnect() { }

} `

Below is my html

`

User List using Table
First Name {{element.FirstName}} Last Name {{element.LastName}}

`

Not getting where exactly the issue is.

theunreal commented 6 years ago

@etovian Can you provide an example of filtering such table?

leocaseiro commented 6 years ago

@theunreal try https://github.com/angular/material2/tree/master/src/material-examples/table-filtering

shrralis commented 6 years ago

EXPLAIN ME please, HOW does it work? When I add new item to data source's array – table doesn't show it immediately. It shows no changes until I click on header for sorting or paginator and only after that table will re-render.

shalugin commented 6 years ago

@Shrralis I think the key in this line - https://github.com/angular/material2/blob/master/src/material-examples/table-filtering/table-filtering-example.ts#L68

shrralis commented 6 years ago

@shalugin for my sorry unfortunately no. I already have something like that:

public addManufacturer(manufacturer: Manufacturer): void {
    let newManufacturers: Manufacturer[] = [];

    this.data.subscribe((manufacturers: Manufacturer[]) => newManufacturers = manufacturers);
    newManufacturers.push(manufacturer);
    this.manufacturerDataSubject.next(newManufacturers);
  }

I know the code looks strange but this is because of my experiments on this to make it work. I can make plnkr with my code.

shalugin commented 6 years ago

Hmmm..

It shows no changes until I click on header for sorting or paginator and only after that table will re-render.

Maybe this indicating a problem. Try insert ChangeDetectorRef into contructor and trigger changes manually.

  constructor(private _cdr: ChangeDetectorRef) {
  }

.....
this.manufacturerDataSubject.next(newManufacturers);
this._cdr.detectChanges();
.....
shrralis commented 6 years ago

Try insert ChangeDetectorRef into contructor and trigger changes manually.

I'm trying with ApplicationRef (tick() method) because of I'm using a service. But there is no effect.

public addManufacturer(manufacturer: Manufacturer): void {
    let newManufacturers: Manufacturer[] = [];

    this.data.subscribe((manufacturers: Manufacturer[]) => newManufacturers = manufacturers);
    newManufacturers.push(manufacturer);
    this.manufacturerDataSubject.next(newManufacturers);
    this._ar.tick();
  }

  public getManufacturers(): Observable<Manufacturer[] | Error> {
    console.log('getManufacturers started!');
    return this.lookupService.getAllManufacturers();
  }

  public submitManufacturers(manufacturers: Observable<Manufacturer[]>): void {
    manufacturers.forEach((m: Manufacturer[]) => {
      console.log('submitManufacturers started! manufacturers: ' + JSON.stringify(m));
    });
    manufacturers.subscribe((m: Manufacturer[]) => this.manufacturerDataSubject.next(m));
    this._ar.tick();
    console.log('submitManufacturers finished!');
  }
shalugin commented 6 years ago

@Shrralis Can you create a plunker or stackblitz with your code?

shrralis commented 6 years ago

@shalugin here it is: https://stackblitz.com/edit/angular-h3ckjw

shalugin commented 6 years ago

@Shrralis

Just change code:

    const displayDataChanges = [
      this._filterChange,
      this._sort.sortChange,
      this._paginator.page,
    ];

to

    const displayDataChanges = [
      this._filterChange,
      this._sort.sortChange,
      this._paginator.page,
      this.manufacturersLocalService.data,
    ];

You don't need to use ApplicationRef (tick() method).

shalugin commented 6 years ago

If I understood you right, then you only need some BehaviorSubject that will trigger changes for DataSource.

irossimoline commented 6 years ago

@tyler2cr , I have created a package that extends angular cdk DataSource implementing row addition, edition and deletion. You can check it here.

It only adds structure to operate with rows, and manage datasource logic to update elements.

This is the npm package.

shrralis commented 6 years ago

@shairez

Just change code

You're right, it worked! Thank you so much

heri-g commented 6 years ago

I have a similar issue. My Angular 4 app has a service pulling data from a Laravel backend thats connected to a MySQL DataBase.

I am able to display the data through ngFor with a Bootstrap 4 table so I know it works.

My question is.. how in the world do I do this with the Material table??

If anyone could provide some guidance i would truly appreciate it.

shalugin commented 6 years ago

@heri-g You can use example from https://github.com/angular/material2/issues/5917#issuecomment-337899837

eltimmo commented 6 years ago

@willshowell Is it possible to get that short example added to the material.angular.io docs?. It's really good and easy to follow. All the other examples are complex and muddy the water with techniques that make it hard to get the basics. Your example showed me what I need to know in 10 minutes, the other just confused me for the last 10 hours, grrr if only I'd found this sooner 👍

shrralis commented 6 years ago

https://github.com/angular/material2/tree/master/src/demo-app/table

What is MatTableDataSource here? Stackblitz gives me error about that.

reemyrail commented 6 years ago

Your plunker code is not really working would you please check on your dependencies @irossimoline

irossimoline commented 6 years ago

@rima-smart , I have updated the dependencies.

tg8888 commented 6 years ago

Here is what worked for me. Stackblitz url :https://stackblitz.com/edit/angular-5zu7px Below is the code to add new element to array without using behaviorSubject.

addRow() {
    alert('adding row');
    this.dataSource.data.push({
      position: 1,
      name: "Hydrogen",
      weight: 1.0079,
      symbol: "H"
    });
    this.dataSource.filter = "";
  }
ORESoftware commented 6 years ago

Is there something less hacky than using

  this.dataSource.filter = "";

after pushing data, to get a re-render?

Also, I add new data, and new rows are added, but the cells in the new row are empty...even though the new data is good.

If I open "Dev Tools" and click around and inspect, then the table will render the cells. So I know something is wrong with this lib.

armansujoyan commented 6 years ago

You can use the method called _updateChangeSubscription(). This method will update subscription to changes that should trigger an update to the table's rendered rows. When the changes occur it processes the current state of the filter, sort, and pagination along with the base data and sends it to the table for rendering.

marinantonio commented 6 years ago

Took me some time but I finally got everything working. 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 test it on gist: https://marinantonio.github.io/angular-mat-table-crud/

ORESoftware commented 6 years ago

@marinantonio cool, how did you create that GIF?

ORESoftware commented 6 years ago

@armansujoyan

I tried this:

      self.dataSource.data.push(m as any);
      self.dataSource._updateChangeSubscription();

unfortunately didn't work...my table renders new rows, but the rows are empty - the cells don't get rendered. This is all very unfortunate. What kind of library is this? I can't add rows to a table? FML.

marinantonio commented 6 years ago

@ORESoftware For recording used free software ScreenToGif, and after that uploaded .gif to Imgur.

paigudepooja commented 6 years ago

@andrewseguin I want to create mat table with dynamic columns and I have array of object in which each object has fields which i want to show it vertically. untitled

In above image I have object where all column fields are in one object and I want to show columns of every object in array. If anyone could provide some guidance i would truly appreciate it.

chethan1095 commented 6 years ago

@marinantonio how to post it permanently you are just pushing it temporary how to post it using post method in angular2

marinantonio commented 6 years ago

@chethan1095 What do you mean? You push post method from DataService.ts and then depending on result (successful or not) you push front-end update to a mat-table.

sukumar89 commented 6 years ago

I need some help, in rendering the value in the data table when the datasource has multiple json array. below is the service json response { "_index": "com_reprocess", "_type": "reprocessor", "_id": "com-payment-intake-62fc632e-5ac3-40d8-a35a-0yment-intake-paypalclosure-q2", "_score": 1, "_source": { "applicationName": "com-payment-intake-paypalclosure-q2", "topic": "COMPayment-PaypalClosure-q2", "retryInterval": 0, "maxRetryCount": 3, "isAutoRetryable": "false", "cassandraTTL": 3000000, "reprocessId": "aafde69c-7379-4bca-ac6e-c2e30bd4be11", "sequenceNo": "1521827002607", "key": "com-payment-intake-62fc632e-5ac3-40d8-a35a-0b2cbd9facf1_WA11121773", "errorCode": "-104", "createTimeStamp": "2018-03-23T17:43:22", "currentRetryCount": 0, "createProgId": "com-payment-intake-paypalclosure-q2_CONSUMER1_fcf59940-37c3-4478-becc-3798e0e0c4a2", "state": "initial", "groupforSameKey": false, "nextRetryTime": "2018-03-23T17:44:22", "clientHeaders": [ {"key": "com-acf1_WA11121773", "value" :"FAILED"} ], "hostIP": "10.255.220.35", "hostName": "dbb6f7b4-4909-479c-79c3-1e95", "isESRecordLocked": false, "modifyTimeStamp": "2018-03-23T17:43:22" } },

where i'm trying to iterate the response and bind them to material datatable. It is all good but i can't access the attributes inside the clientHeaders array.

here is my material table html statements and it is not returning any value.

  <ng-container matColumnDef="value" style="column-width:500px">
    <mat-header-cell *matHeaderCellDef mat-sort-header [ngClass]="'value'"> fail_rsn </mat-header-cell>
    <mat-cell *matCellDef="let element" [ngClass]="'value'"> {{element._source.clientHeaders.key}} </mat-cell>
  </ng-container> Can any one help on this ? stuck with 2 days on this 
Oyedele-Sg commented 6 years ago

@andrewseguin Great job man! Can you please share a link to the repository for the updates you made that includes adding/removing/editing rows and if possible, editing individual cells. Thank you.

rain01 commented 6 years ago

Are the examples still coming to the documentation?

homj commented 6 years ago

Vielen Dank für Ihre Nachricht.

Ich bin vom 25. Juni bis 1. Juli im Urlaub. Ab Montag, dem 2. Juli bin ich wieder gerne für Sie erreichbar.

Ihre E-Mail wird an info@bynary.de weitergeleitet. In dringenden Fällen wenden Sie sich bitte an Sebastian Homeier (E-Mail: s.homeier@bynary.de, Tel.: +49 (0) 941 462 971 30).

Danke für Ihr Verständnis!

Viele Grüße Johannes Homeier

TalLoss commented 6 years ago

@andrewseguin This is really important! Is an example coming out soon?