Components: [Table] simpler example without example database

Created on 26 Jul 2017  路  26Comments  路  Source: angular/components

Bug, feature request, or proposal:

Docs request

Motivation

https://github.com/angular/material2/issues/5917#issuecomment-316936589

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

Most helpful comment

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>

All 26 comments

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.

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.

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.

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?

@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

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.

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.

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>

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

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

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

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 theconst 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

create a material looking table (withOUT dataTable) #3805

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

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

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

@utk23dubey can you post final code.

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

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

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

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

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.

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';

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

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.

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