Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Table] simpler example without example database #6036

Closed
willshowell opened this issue Jul 25, 2017 · 26 comments · Fixed by #6177
Closed

[Table] simpler example without example database #6036

willshowell opened this issue Jul 25, 2017 · 26 comments · Fixed by #6177
Assignees

Comments

@willshowell
Copy link
Contributor

willshowell commented Jul 25, 2017

Bug, feature request, or proposal:

Docs request

Motivation

#5917 (comment)

Also, many of the table-related questions posted here and on stack overflow show that folks don't really understand the purpose and use of ExampleDatabase. They are trying to copy/paste/mold it to fit their solution, when it really is just a support class for the example.

Proposed Example

I think first-time readers would greatly benefit from seeing a single-emission data source without any frills. Then, once they want to know how to make their table more interesting and dynamic, they can learn about the benefits of Subjects, DAOs, etc in the more complicated examples with a better understanding of how the foundation works.

interface Pet {
  name: string;
  type: string;
  age: number;
}


@Component({ ... })
export class TableSimpleExample {
  myPets = [
    { name: 'Boots', type: 'Cat', age: 2 },
    ...
  ];

  dataSource: PetDataSource;

  ngOnInit() {
    this.dataSource = new PetDataSource(this.myPets);
  }
}


export class PetDataSource extends DataSource<Pet> {

  constructor(private pets: Pet[]) {
    super();
  }

  connect(): Observable<Pet[]> {
    return Observable.of(this.pets);
  }
}
@iposton
Copy link

iposton commented Jul 26, 2017

I agree with @willshowell about the md-table example being too complex https://material.angular.io/guide/cdk-table.

For example, I am using the HTTPModule to access my data from an api. I have a JSON res coming back, and I would like to simply add this res to the DataSource for the table to render. I prefer something similar to *ngFor to access json data in the view. I can not figure out how to pass in my JSON data response into the DataSource for the table with the example link above. If anybody has a helpful suggestion please share.

And thank you to the angular-material team for making really amazing stuff for us to use.

import { Component } from '@angular/core';
import { DataSource } from '@angular/cdk';
import { MdSort } from '@angular/material';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Http, Response, RequestOptions, Headers, Request, RequestMethod } from '@angular/http';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';

export interface Data {}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  
  title = 'app';
  myData: Array < any > ;
  displayedColumns = ['id', 'name'];
  dataSource: MyDataSource;

  constructor(private http: Http) {
    this.getData();
  }

  public getData() {
    let url = 'https://api.mydatafeeds.com/v1.1/cumulative_player_data.json?';
    let headers = new Headers({ "Authorization": "123ykiki456789123456" });
    let options = new RequestOptions({ headers: headers });
    this.http.get(url, options)
      .map(response => response.json())
      .subscribe(res => {
        this.myData = res;
        this.dataSource = new MyDataSource(this.myData);
      });
  }
}

export class MyDataSource extends DataSource<any> {
  constructor(private data: Data[]) {
    super();
  }
   /** Connect function called by the table to retrieve one stream containing the data to render. */
  connect(): Observable<Data[]> {
    return Observable.of(this.data);
  }

  disconnect() {}

  }

Edit: I made some changes based on the simple code example above. Now my <md-table #table [dataSource]="dataSource"> is seeing my json data and repeating each row based on the length of the json array. But nothing is rendered in the view. The table won't allow me to use dot notation to get the nested repeated data. Any suggestions? for example:

<md-table #table [dataSource]="dataSource">  
    <ng-container cdkColumnDef="id">
      <md-header-cell *cdkHeaderCellDef> Id </md-header-cell>
      <md-cell *cdkCellDef="let data"> <b>{{data.person.id}}.</b> 
      </md-cell>
    </ng-container>

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

This doesn't work. *ngFor example I would get my same json data like this:

<div *ngFor="let data of myData">
{{data.person.id}}
</div>

edit: Nevermind. It works now! I can render my json response in the md-table.

@willshowell
Copy link
Contributor Author

Maybe an explanation similar to this could accompany the example,

By using a single emission Observable, this example shows how to render a set of rows once. If you wish to re-render based on user input (e.g. sorting, filtering, etc) or other stimuli (e.g. interval, new data), you'll need to connect to a stream of values that emits for each presentational change.

With the multitude of data sources you may wish to connect to your table, each implementation will be unique, but there are a variety of techniques you can employ in doing so. For example,

  1. Merge EventEmitters like paginator events and other Observables, such as an FormControl's valueChanges or a socket event, to source a new change.
  2. Use RxJS operators like filter and map to convert your trigger events into data compatible with your table columns. Use switchMap to map a trigger event to a new Observable, like a BehaviorSubject or HTTP request.

@jmpreston
Copy link

Just connecting to an external db such as Firebase for the example data would be a very good thing for us newbies! Also, why does the Plunker basic data table example import every MD module? This doesn't seem necessary.

@andrewseguin andrewseguin self-assigned this Jul 27, 2017
@mitidiero
Copy link

I'm missing an example connecting to an external DB and doing things like Sorting, Filtering, etc on server-side. Is it possible with MdTable?

@jmpreston
Copy link

@mitidiero Here's how to connect to a client side service in Angular 4 and Firebase. I'm trying to figure out filtering now.

https://stackoverflow.com/questions/45394588/angular-material-2-data-table-setup-as-master-detail/45394589#45394589

@pevans360
Copy link

The @iposton example got me over the hump on this. Just changed 'md-' to 'mat-' and 'cdkxxx' to 'matxxx' and off to the races. Thanks much.

@KEMBL
Copy link

KEMBL commented Nov 28, 2017

Another simplified generc data source:

  import { Observable } from 'rxjs/Observable';

   // Simple data source for using it in mat-table
  export class SimpleDatasource <T> {
    // array of assoc arrays with table rows
   private data: T;

     // Use it to fill in a new data
    setData(newData: T) {
      this.data = newData;
    }

     // Table will call it in order to use data array
    connect(): Observable<T> {
      return Observable.of(this.data);
    }

    disconnect(): void {
      // nothing here, called by table when it is destroyed
    }
  }

usage:
in the component:

  dataSource: SimpleDatasource<UserListRecordModel[]>;
  constructor(){ dataSource = new SimpleDatasource<SomeTableRowModel[]>(); }
  someCallbackMethodOrOtherPlaceWhereYouReceivedData() { dataSource.setData(arrayOfRows); }

in template

  <mat-table #table [dataSource]="dataSource">...

also, you can add a constructor to SimpleDatasource and init data with it.

@domske
Copy link

domske commented Nov 29, 2017

Anyway it would be possible to design a table completely in HTML. Without data source binding. Like:

<mat-table>
  <mat-header-row>
    <mat-header-cell>One</mat-header-cell>
    <mat-header-cell>Two</mat-header-cell>
    <mat-header-cell>Three</mat-header-cell>
  </mat-header-row>
  <mat-row>
    <mat-cell>Foo</mat-cell>
    <mat-cell>Bar</mat-cell>
    <mat-cell>Haha</mat-cell>
  </mat-row>
</mat-table>

Or conventionally:

<table>
  <thead>
    <tr>
      <th>One</th>
      <th>Two</th>
      <th>Three</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let item of items;">
        <td>{{item.one}}</td>
        <td>{{item.two}}</td>
        <td>{{item.three}}</td>
    </tr>
  </tbody>
</table>

@utk23dubey
Copy link

Can anybody help in using filter and pagination after getting data from the server

@KEMBL
Copy link

KEMBL commented Nov 29, 2017

@utk23dubey take a look at this article: https://material.angular.io/components/table/overview

@utk23dubey
Copy link

@KEMBL thanks for the help.But i have gone thru it already.:)

@iposton
Copy link

iposton commented Nov 29, 2017

I originally set up my example above using the paginator table. I had it working, but didn't like how it reset my index when I clicked a new page. I didn't save it, but I think this is how I made it work. I have not tested this in a while so it might not be what you are looking for, but maybe it can help :)

import { Component, ViewChild } from '@angular/core';
import { DataSource } from '@angular/cdk';
import { MatPaginator } from '@angular/material';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Http, Response, RequestOptions, Headers, Request, RequestMethod } from '@angular/http';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';

export interface Data {}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  
  title = 'app';
  myData: Array < any > ;
  displayedColumns = ['id', 'name'];
  dataSource: MyDataSource;

@ViewChild(MatPaginator) paginator: MatPaginator;

  constructor(private http: Http) {
    this.getData();
  }

  public getData() {
    let url = 'https://api.mydatafeeds.com/v1.1/cumulative_player_data.json?';
    let headers = new Headers({ "Authorization": "123ykiki456789123456" });
    let options = new RequestOptions({ headers: headers });
    this.http.get(url, options)
      .map(response => response.json())
      .subscribe(res => {
        this.myData = res;
        this.dataSource = new MyDataSource(this.myData, this.paginator);
      });
  }
}

export class MyDataSource extends DataSource<any> {
  constructor(private dataBase: Data[],  private paginator: MatPaginator) {
    super();
  }
   /** Connect function called by the table to retrieve one stream containing the data to render. */
  connect(): Observable<Data[]> {
    //return Observable.of(this.dataBase);
    const displayDataChanges = [
      //Observable.of(this.dataBase),
      this.dataBase.dataChange,
      this.paginator.page,
    ];

    return Observable.merge(...displayDataChanges).map(() => {
      const data = this.dataBase.slice();
      // //console.log(data, 'merge');
      // // Grab the page's slice of data.
      const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
      const finalData = data.splice(startIndex, this.paginator.pageSize);
    
      // console.log(finalData, 'finalData')
      return finalData;
  
    });
  }

  disconnect() {}

  }
<md-table #table [dataSource]="dataSource">  
    <ng-container cdkColumnDef="id">
      <md-header-cell *cdkHeaderCellDef> Id </md-header-cell>
      <md-cell *cdkCellDef="let data"> <b>{{data.person.id}}.</b> 
      </md-cell>
    </ng-container>

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

   <mat-paginator #paginator
                [length]="myData.data.length"
                [pageIndex]="0"
                [pageSize]="25"
                [pageSizeOptions]="[5, 10, 25, 100]">
    </mat-paginator>

</md-table>

Update: Wow I was missing quite a few important lines to make this work. I forgot to inject the private paginator: MatPaginator into the constructor of the MyDataSource export. This way paginator is defined in the return function below. Also added this line [length]="myData.data.length" to the html. I think that's the dataSource I used as defined in the Appcomponent. I changed this line as well private dataBase: Data[] so it wouldn't muddle with the const data below to provide a little less confusion.

this.dataSource = new MyDataSource(this.myData, this.paginator); I forgot this.paginator needs to be passed into this.dataSource

@domske
Copy link

domske commented Nov 29, 2017

create a material looking table (withOUT dataTable) #3805

@utk23dubey
Copy link

Having errors while using sort and filter the table:(.None of the examples are helping me out

@iposton
Copy link

iposton commented Nov 30, 2017

@utk23dubey sorry to hear this. I tried to use sort and paginator together and I think it was no bueno. I could be wrong but I think you can only use one angular material2 feature for the data table. But if you can post your code or the error messages I can try to help you with your project.

@utk23dubey
Copy link

utk23dubey commented Nov 30, 2017

@iposton After so much of hours spend.Finally my code works.Your Example helped me a lot:).
Thanks Sir :)

@nikhilb1208
Copy link

@utk23dubey can you post final code.

@utk23dubey
Copy link

@nikhilb1208 just see the above code which is posted and do changes according to your need.My code is just the revised version of this code only.

1 similar comment
@utk23dubey
Copy link

@nikhilb1208 just see the above code which is posted and do changes according to your need.My code is just the revised version of this code only.

@aracis42
Copy link

For me the above code is not working. Sometimes I have the feeling that the name Angular came from anger, that is my feeling as a noob when I try to learn something new. Why is it so hard to provide a little peace of running code on the official website that gives a starting point to develop further. Breaking changes in the naming of tags and in the import sources are real nails for my coffin

@willshowell
Copy link
Contributor Author

@aracis42 it's worth noting that most of the conversation in this thread occurred while Material was in beta and was initially focused on making the documentation better. Github issues typically aren't the best place to expect evergreen code, far less so for a library that is actively evolving around user feedback.

There are loads of people on StackOverflow that are happy to help troubleshoot issues with you, and would probably be happy to do here as well! But simply saying the code (which code?) is not working (how is it not working?) in your unique context isn't going to get you to an answer.

@aracis42
Copy link

The code above from iposton above is not working because even the tags like are deprecated in newer versions. The description in https://material.angular.io/components/table/overview#sorting is IMHO not really helpful. Never mind, I give up with material tables and switch over to ag-grid.

@iposton
Copy link

iposton commented Dec 23, 2017

This Example Has Been Updated to comply with Angular 5 and latest material2 release. April 12, 2018

@aracis42 I feel your pain. I have added a sample using matSort. My previous example was for a plain table and for a matPaginator table. Maybe this can help you. angular material changed from mdSort to matSort shortly after I posted my original sample. It should be as easy as changing everything that starts with md to mat. Good Luck!

import { Component, ViewChild, Inject, OnInit, ElementRef } from '@angular/core';
import { MatTableDataSource, MatSort } from '@angular/material';
import { DataSource } from '@angular/cdk/table';
import { Observable } from 'rxjs/Observable';
import { HttpClient, HttpResponse, HttpHeaders, HttpRequest} from '@angular/common/http';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';

export interface Data {}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {

  myData: Array < any > ;
  displayedColumns = ['id', 'name'];

  dataSource: MyDataSource;

  @ViewChild(MatSort) sort: MatSort;

  constructor(private http: HttpClient) {}

 getData() {
    let url = 'https://api.mydatafeeds.com/v1.1/cumulative_player_data.json?';
    let headers = new HttpHeaders({ "Authorization": "123ykiki456789123456" });
    this.http.get(url, {headers})
      .subscribe(res => {
        this.myData = res;
        this.dataSource = new MyDataSource(this.myData, this.sort);
      });
  }

  ngOnInit() {
    this.getData();
  }
}

export class MyDataSource extends DataSource < any > {
  constructor(private dataBase: Data[], private sort: MatSort) {
    super();
  }
  /** Connect function called by the table to retrieve one stream containing the data to render. */
  connect(): Observable < Data[] > {
    const displayDataChanges = [
      Observable.of(this.dataBase),
      this.sort.sortChange,
    ];

    return Observable.merge(...displayDataChanges).map(() => {
      return this.getSortedData();
    });
  }

  disconnect() {}

  /** Returns a sorted copy of the database data. */
  getSortedData(): Data[] {
    const data = this.dataBase.slice();
    if (!this.sort.active || this.sort.direction == '') { return data; }

    return data.sort((a, b) => {

      let propertyA: number | string = '';
      let propertyB: number | string = '';

      switch (this.sort.active) {
        case 'id':
          [propertyA, propertyB] = [a.id, b.id];
          break;
        case 'name':
          [propertyA, propertyB] = [a.name, b.name];
          break;

      }

      let valueA = isNaN(+propertyA) ? propertyA : +propertyA;
      let valueB = isNaN(+propertyB) ? propertyB : +propertyB;

      return (valueA < valueB ? -1 : 1) * (this.sort.direction == 'asc' ? 1 : -1);
    });

  }

}
<mat-table #table [dataSource]="dataSource" matSort>
  <ng-container matColumnDef="id">
    <mat-header-cell *matHeaderCellDef mat-sort-header> Id </mat-header-cell>
    <mat-cell *matCellDef="let data"> <b>{{data.id}}.</b>
    </mat-cell>
  </ng-container>
  <ng-container matColumnDef="name">
    <mat-header-cell *matHeaderCellDef mat-sort-header> Id </mat-header-cell>
    <mat-cell *matCellDef="let data"> <b>{{data.name}}.</b>
    </mat-cell>
  </ng-container>
  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let data; columns: displayedColumns;"></mat-row>
</mat-table>

Update: I added the new HttpClient to this example. And I am using the old sort example from an older version of angular-material. The latest example uses ngAfterViewInit() to call sort this.dataSource.sort = this.sort; I was unable to get sort to work using the new example. The older sort method uses extends DataSource < any >. I was able to import DataSource using a new path import { DataSource } from '@angular/cdk/table';

@deepali-kakkar
Copy link

@iposton can you also provide me the same example for filtering the mat table. Thanks so much..!!

@iposton
Copy link

iposton commented Jan 20, 2018

This Example Has Been Updated to comply with Angular 5 and latest material2 release. April 12, 2018

@deepali-kakkar this sounds fun. Let me try to get it working in my app and if I do I will post the code here.

import { Component, ViewChild, Inject, OnInit, ElementRef } from '@angular/core';
import { MatTableDataSource, MatSort } from '@angular/material';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { DataSource } from '@angular/cdk/table';
import { Observable } from 'rxjs/Observable';
import { HttpClient, HttpResponse, HttpHeaders, HttpRequest} from '@angular/common/http';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/observable/fromEvent';

export interface Data {}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {

  myData: Array < any > ;
  displayedColumns = ['id', 'name'];
  dataSource: MyDataSource;

 @ViewChild('filter') filter: ElementRef;

  constructor(private http: HttpClient) {}

  public getData() {
    let url = 'https://api.mydatafeeds.com/v1.1/cumulative_player_data.json?';
    let headers = new HttpHeaders({ "Authorization": "123ykiki456789123456" });
    this.http.get(url, {headers})
      .subscribe(res => {
        this.myData = res;
        this.dataSource = new MyDataSource(this.myData);
      });
  }

ngOnInit() {
   
      this.getData();
      Observable.fromEvent(this.filter.nativeElement, 'keyup')
        .debounceTime(150)
        .distinctUntilChanged()
        .subscribe(() => {
          if (!this.dataSource) { return; }
          this.dataSource.filter = this.filter.nativeElement.value;
        });

}

export class MyDataSource extends DataSource < any > {

   _filterChange = new BehaviorSubject('');
   get filter(): string { return this._filterChange.value; }
   set filter(filter: string) { this._filterChange.next(filter); }

  constructor(private dataBase: Data[]) {
    super();
  }
  /** Connect function called by the table to retrieve one stream containing the data to render. */
  connect(): Observable < Data[] > {
    const displayDataChanges = [
       this._filterChange
    ];

   return Observable.merge(...displayDataChanges).map(() => {
      return this.dataBase.slice().filter((item: Data) => {
        let searchStr = (item.name).toLowerCase();
        return searchStr.indexOf(this.filter.toLowerCase()) != -1;
      });
    });
  }

  disconnect() {}

}
 <div class="example-header">
    
      <input matInput #filter placeholder="Filter by name">
   
  </div>

 <mat-table #table [dataSource]="dataSource">
  <ng-container matColumnDef="id">
    <mat-header-cell *matHeaderCellDef> Id </mat-header-cell>
    <mat-cell *matCellDef="let data"> <b>{{data.id}}.</b>
    </mat-cell>
  </ng-container>
  <ng-container matColumnDef="name">
    <mat-header-cell *matHeaderCellDef> Id </mat-header-cell>
    <mat-cell *matCellDef="let data"> <b>{{data.name}}.</b>
    </mat-cell>
  </ng-container>
  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let data; columns: displayedColumns;"></mat-row>
</mat-table>

@deepali-kakkar Update: I tested this and it worked with a json response from an api. The big difference from my other examples is using ngOnInit to watch for changes when a user starts typing in the input. I inject it here import { Component, ViewChild, Inject, OnInit, ElementRef } from '@angular/core'; also adding ElementRef to define the filter. To use ngOnInit in the component make sure the export is this export class AppComponent implements OnInit.

I added these imports from the rxjs library.

import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/observable/fromEvent';

I want to point out that you will need to include import {FormsModule, ReactiveFormsModule} from '@angular/forms'; in the app.module.ts file. And include import { MatInputModule } from '@angular/material';. Then add these imports to @NgModule imports along with other Material modules you wish to import. I hope this can help you :)

ps the original example on angular-material's website used <mat-form-field> in the html, but it was causing errors in my example so I excluded it. If you can figure it out on your own please let me know if you get the mat input to work properly. Thanks.

@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.