12

I am quite new in the angular world and i'm trying to use the new md-table component in Angular Material 2 with Angular 4.

I've made a service from an API which retrieves simple arrays of content. Now I'm trying to use this service as a data source for the md-table but I can't find a way to get the data from the service (it always return me an empty array).

Please note that before using md-table, I was using already using the service and it worked normally.

Here is the code for the component :

import { Component, OnInit, ViewChild } from '@angular/core';
import {DataSource} from '@angular/cdk';
import {MdPaginator} from '@angular/material';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';

import { GroupService } from '../shared/group.service';
import { Group } from '../shared/group';

@Component({
  selector: 'app-group-list',
  templateUrl: './group-list.component.html',
  styleUrls: ['./group-list.component.css'],
  providers: [GroupService]
})
export class GroupListComponent implements OnInit{

  public DisplayedColumns = ['name', 'email', 'directMembersCount'];
  public groupDatabase = new GroupDatabase();
  public dataSource : CustomDataSource | any;

  @ViewChild(MdPaginator) paginator : MdPaginator;

  constructor() {}

  ngOnInit() {
    this.dataSource = new CustomDataSource(this.groupDatabase, this.paginator);
    console.log(this.dataSource);
  }

}

export class GroupDatabase implements OnInit {

  public dataChange: BehaviorSubject<Group[]> = new BehaviorSubject<Group[]>([]);
  get data(): Group[] { return this.dataChange.value }

  private _groupService : GroupService

  private getAllGroups(){
    return this._groupService
      .getAllGroups();
  }

  constructor (){}

  ngOnInit() {
      this.getAllGroups();
      console.log(this.getAllGroups());
  }
}

export class CustomDataSource extends DataSource<any> {

  constructor(private _groupDatabase = new GroupDatabase(), private _paginator: MdPaginator){
    super();
  }

  connect(): Observable<Group[]> {
    const displayDataChanges = [
      this._groupDatabase.dataChange,
      this._paginator.page
    ];
    return Observable.merge(...displayDataChanges).map(() => {
      const data = this._groupDatabase.data.slice();
      console.log(data);

      const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
      return data.splice(startIndex, this._paginator.pageSize);
    })
  }

  disconnect() {}
}

Here is the code for the HTML :

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

  <ng-container *cdkColumnDef="name">
    <md-header-cell *cdkCellDef>Nom</md-header-cell>
    <md-cell *cdkCellDef="let row"> {{row.name}} </md-cell>
  </ng-container>

  <ng-container *cdkColumnDef="email">
    <md-header-cell *cdkCellDef>Email</md-header-cell>
    <md-cell *cdkCellDef="let row"> {{row.email}} </md-cell>
  </ng-container>

  <ng-container *cdkColumnDef="directMembersCount">
    <md-header-cell *cdkCellDef>Nombre de membres</md-header-cell>
    <md-cell *cdkCellDef="let row"> {{row.directMembersCount}} </md-cell>
  </ng-container>

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

</md-table>

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

And the concerned service :

private groupApiUrl: string;
  private groupsApiUrl: string;
  private headers: Headers;
  constructor(public http:Http, private config: Config) {
    this.groupApiUrl = config.serverWithApi + "group";
    this.groupsApiUrl = config.serverWithApi + "groups";

    this.headers = new Headers();
    this.headers.append('Content-Type', 'application/json');
    this.headers.append('Accept', 'application/json');
  }

  public getAllGroups = (): Observable<Group[]> => {
    return this.http.get(this.groupsApiUrl)
      .map((response: Response) => <Group[]>response.json())
      .catch(this.handleError)
  }

I'm not sure how I should call the service using the datasource, that's why I did it as I was doing before; using the ngOnInit method.

Thanks for you help.

Helongh
  • 153
  • 1
  • 1
  • 9
  • Looks like I'm not the only on wondering this ! An issue about that has been posted on the angular repo : [#5670](https://github.com/angular/material2/issues/5670) – Helongh Jul 11 '17 at 12:33
  • There doesn't seem to be any documentation on the angular.io site specifically for DataSource, it is only referenced when discussing other subjects, such as cdk tables. The part I am getting stuck on is populating the DataSource after user performs a search. – Wallace Howery Oct 24 '17 at 18:15

1 Answers1

15

Here's an example of retrieving data through HTTP: https://plnkr.co/edit/mjQbufh7cUynD6qhF5Ap?p=preview


You are missing a piece where GroupDatabase does not put any data values on the dataChange stream. It is a BehaviorSubject that starts with an empty array but you do not put any more data on it. This is why the table is only receiving an empty array.

First, know that ngOnInit will not be called by Angular since GroupDatabase is not a directive and is not part of the change detection cycle.

Instead, move this.getAllGroups() to the constructor of GroupDatabase. and then subscribe to its result:

export class GroupDatabase {
  public dataChange: BehaviorSubject<Group[]> = new BehaviorSubject<Group[]>([]);
  get data(): Group[] { return this.dataChange.value }

  constructor(groupService: GroupService) {
    groupService.getAllGroups().subscribe(data => this.dataChange.next(data));
  }
}

Alternatively, get rid of GroupDatabase altogether and have your CustomDataSource directly call your GroupService.

export class CustomDataSource extends DataSource<Group> {

  constructor(
      private _groupService: GroupService, 
      private _paginator: MdPaginator) { }

  connect(): Observable<Group[]> {
    const displayDataChanges = [
      this._groupService.getAllGroups(),
      this._paginator.page
    ];

    return Observable.merge(...displayDataChanges).map((data, page) => {
      const clonedData = data.slice();

      const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
      return data.splice(startIndex, this._paginator.pageSize);
    })
  }

  disconnect() {}
}
Andrew Seguin
  • 3,042
  • 1
  • 12
  • 9
  • 1
    Thanks for your answer. Indeed this solved my issue, but the doc really i confusing ! – Helongh Jul 19 '17 at 06:57
  • Using the first method with the GroupDatabase, how/when do you pass in the service? See my question at https://stackoverflow.com/questions/46456808/angular-5-material-table-not-getting-data-from-service/46458726#46458726 for more specifics on the question and to see what I tried. – Tim Sep 28 '17 at 14:25
  • @Andrew: I'am following the your example on Plunker which is working fine with `GitHub API` but when I change the URL and add mine `http://localhost:4000/users`, I get following error: `ERROR TypeError: t.json(...).map is not a function`. I assume it's affecting this part of code: `return result.json().map(issue => {return {....}});`. Any idea what should be fixed there? Thanks. – k.vincent Oct 09 '17 at 11:37
  • @k.vincent that basically means your `t` is basically not formatted properly. Have you tried logging `t`? What type is it? – carkod Aug 13 '18 at 22:37