drunkZombie
drunkZombie

Reputation: 163

Creating datasource for nested object in Angular 8

I am trying my hands on Angular for a project and I am stuck with creating a data source for my MatTable.

The API that I am consuming sends response like this:

{
    data: [], //array of objects for samples
    total: 100, //total number of samples found
    pageSize: 10,
    pageIndex: 0
}

My model for samples is:

//Model for a single sample
export class Sample {
    id: number;
    name: string;
}

//a plural namespace is used for multiple samples returned from API
export class Samples {
    samples: Sample[],
    total: number;
    pageSize: number;
    pageIndex: number;
}

// a const in case no samples were found
export const NO_SAMPLES {
    total: 0,
    pageIndex: 0,
    pageSize: 0,
    samples: []
}

Now when I am trying to integrated this with a data source like below:

... //required imports
export class SamplesDataSource extends DataSource<Samples> {

private samplesSubject = new BehaviorSubject<Samples>(NO_SAMPLES);

public constructor(private samplesService: SamplesService) {
    super();
}

//this is where the error (described below) is showing
connect(collectionViewer: CollectionViewer): Observable<Samples> {
    return this.samplesSubject.asObservable();
}

disconnect(collectionViewer: CollectionViewer): void {
    this.samplesSubject.complete();
}

getSamples() {
    this.samplesService.getSamples().pipe(
        catchError(err => {
            return of([]);
        })
    ).subscribe(
        samples => this.samplesSubject.next(samples)
    );

}

}

But it shows me error message:

ERROR in src/app/shared/data-sources/samples-data-source.ts(20,5): error TS2416: Property 'connect' in type 'SamplesDataSource' is not assignable to the same property in base type 'DataSource'

How can I handle this case.

Please note, I need to retain total, pageSize and pageIndex for my paginator to work in align with the paginator at backend.

Thanks in advance.

Edit

App component.ts

    ... //required imports
export class AlertCasesComponent implements OnInit {
  ...

  samplesTableColumns: string[] = ['sampleId', 'sample'];
  samplesTablePageSizes: number[] = [10, 20, 50, 100];
  samplesDataSource: SamplesDataSource;

  ...

  constructor(samplesService: SamplesService) {
    ...
  }

  ngOnInit(): void {
      this.samplesDataSource = new SamplesDataSource(this.samplesService);
      this.samplesDataSource.getSamples();
      ...
  }

  ...

}

App Component.html

<mat-table class="..." [dataSource]="samplesDataSource.samples">

    .... //rows and columns implementations

</mat-table>

<mat-paginator [length]="samplesDataSource.total" pageIndex="0" [pageSize]="samplesDataSource.pageSize" [pageSizeOptions]="samplesTablePageSizes" showFirstLastButtons></mat-paginator>

Upvotes: 1

Views: 3060

Answers (1)

Valeriy Katkov
Valeriy Katkov

Reputation: 40602

It's because the DataSource.connect() should return an observable that emits an array of data. So, the SamplesDataSource should return array of samples, like:

export class SamplesDataSource extends DataSource<Sample> {
  private samplesSubject = new BehaviorSubject<Samples>(NO_SAMPLES);

  connect(collectionViewer: CollectionViewer): Observable<Sample[]> {
    return this.samplesSubject.asObservable().pipe(
      map(samples => samples.samples)
    );
  }

  ...
}

Here is the official CDK table example, I hope it will help you. stackblitz version is also available.

Update:

It isn't necessary to use DataSource class in your case, you can use an array of samples as the data source. I've created the stackblitz example.

table-basic-example.ts

import { Component, Injectable } from '@angular/core';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { switchMap, delay } from 'rxjs/operators';
import { PageEvent } from '@angular/material/paginator';

export interface Sample {
  id: number;
  name: string;
}

export interface Samples {
  samples: Sample[];
  total: number;
  pageSize: number;
  pageIndex: number;
}

const ELEMENT_DATA: Sample[] = [
  {id: 1, name: 'Hydrogen'},
  {id: 2, name: 'Helium'},
  {id: 3, name: 'Lithium'},
  {id: 4, name: 'Beryllium'},
  {id: 5, name: 'Boron'},
  {id: 6, name: 'Carbon'},
  {id: 7, name: 'Nitrogen'},
  {id: 8, name: 'Oxygen'},
  {id: 9, name: 'Fluorine'},
  {id: 10, name: 'Neon'}
];

@Injectable()
export class SamplesService {
  getSamples(pageIndex: number, pageSize: number): Observable<Samples> {
    const start = pageIndex * pageSize;
    const samples = ELEMENT_DATA.slice(start, start + pageSize);
    return of<Samples>({
      samples: samples,
      pageIndex: pageIndex,
      pageSize: pageSize,
      total: ELEMENT_DATA.length
    }).pipe(
      delay(500)
    );
  }
}

@Component({
  selector: 'table-basic-example',
  templateUrl: 'table-basic-example.html',
  styleUrls: ['table-basic-example.css'],
  providers: [ SamplesService ]
})
export class TableBasicExample {
  readonly displayedColumns: string[] = ['id', 'name'];
  readonly page$ = new BehaviorSubject({
    index: 0,
    size: 4
  });

  samples: Samples = {
    total: 0,
    pageIndex: 0,
    pageSize: 0,
    samples: []
  };

  constructor(
    private readonly samplesService: SamplesService
  ) {
    this.page$.pipe(
      switchMap(page => {
          return this.samplesService.getSamples(page.index, page.size);
      })
    ).subscribe(samples => {
      this.samples = samples;
    });
  }

  onPageChanged(event: PageEvent) {
    this.page$.next({
      index: event.pageIndex,
      size: event.pageSize
    });
  }
}

table-basic-example.html

<table mat-table [dataSource]="samples.samples" class="mat-elevation-z8">
  <ng-container matColumnDef="id">
    <th mat-header-cell *matHeaderCellDef> No. </th>
    <td mat-cell *matCellDef="let element"> {{element.id}} </td>
  </ng-container>

  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef> Name </th>
    <td mat-cell *matCellDef="let element"> {{element.name}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

<mat-paginator
  [length]="samples.total"
  pageIndex="pageIndex$.value"
  [pageSize]="samples.pageSize"
  [pageSizeOptions]="[4, 9]"
  showFirstLastButtons
  (page)="onPageChanged($event)"></mat-paginator>

Upvotes: 3

Related Questions