30

I am trying to Achieve Angular Material 2, MatPaginator server side paging. How can I achieve that?

Below is the code example:

  <div class="example-container mat-elevation-z8">
  <mat-table #table [dataSource]="dataSource">

    <!-- Position Column -->
    <ng-container matColumnDef="position">
      <mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.position}} </mat-cell>
    </ng-container>

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

    <!-- Weight Column -->
    <ng-container matColumnDef="weight">
      <mat-header-cell *matHeaderCellDef> Weight </mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.weight}} </mat-cell>
    </ng-container>

    <!-- Symbol Column -->
    <ng-container matColumnDef="symbol">
      <mat-header-cell *matHeaderCellDef> Symbol </mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.symbol}} </mat-cell>
    </ng-container>

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

  <mat-paginator #paginator
                 [pageSize]="10"
                 [pageSizeOptions]="[5, 10, 20]">
  </mat-paginator>
</div>

Pagination Component:

import {Component, ViewChild} from '@angular/core';
import {MatPaginator, MatTableDataSource} from '@angular/material';

/**
 * @title Table with pagination
 */
@Component({
  selector: 'table-pagination-example',
  styleUrls: ['table-pagination-example.css'],
  templateUrl: 'table-pagination-example.html',
})
export class TablePaginationExample {
  displayedColumns = ['position', 'name', 'weight', 'symbol'];
  dataSource = new MatTableDataSource<Element>(ELEMENT_DATA);

  @ViewChild(MatPaginator) paginator: MatPaginator;

  /**
   * Set the paginator after the view init since this component will
   * be able to query its view for the initialized paginator.
   */
  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
  }
}

export interface Element {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: Element[] = [
  {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
  {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
  {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
  {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
  {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
  {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
  {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
  {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
  {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
  {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
  {position: 11, name: 'Sodium', weight: 22.9897, symbol: 'Na'},
  {position: 12, name: 'Magnesium', weight: 24.305, symbol: 'Mg'},
  {position: 13, name: 'Aluminum', weight: 26.9815, symbol: 'Al'},
  {position: 14, name: 'Silicon', weight: 28.0855, symbol: 'Si'},
  {position: 15, name: 'Phosphorus', weight: 30.9738, symbol: 'P'},
  {position: 16, name: 'Sulfur', weight: 32.065, symbol: 'S'},
  {position: 17, name: 'Chlorine', weight: 35.453, symbol: 'Cl'},
  {position: 18, name: 'Argon', weight: 39.948, symbol: 'Ar'},
  {position: 19, name: 'Potassium', weight: 39.0983, symbol: 'K'},
  {position: 20, name: 'Calcium', weight: 40.078, symbol: 'Ca'},
];

How can I achieve server side pagination, which would trigger on change event of next page click or page size changes to get next set of records.

https://stackblitz.com/angular/qxxpqbqolyb?file=app%2Ftable-pagination-example.ts

Freestyle09
  • 4,894
  • 8
  • 52
  • 83
VR1256
  • 1,266
  • 4
  • 28
  • 56

3 Answers3

22

Based on Wilfredo's answer (https://stackoverflow.com/a/47994113/986160) I compiled a full working example since some pieces were missing from question as well. Here is a more general case for server-side pagination and sorting using Angular 5 and Material Design (still need to plug in filtering) - hopefully it will be helpful to someone:

Paging Component:

import { ViewChild, Component, Inject, OnInit, AfterViewInit } from '@angular/core';
import { EntityJson } from './entity.json';
import { EntityService } from './entity.service';
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { merge } from 'rxjs/observable/merge';
import { of as observableOf } from 'rxjs/observable/of';
import { catchError } from 'rxjs/operators/catchError';
import { map } from 'rxjs/operators/map';
import { startWith } from 'rxjs/operators/startWith';
import { switchMap } from 'rxjs/operators/switchMap';

@Component({
    selector: 'entity-latest-page',
    providers: [EntityService],
    styles: [`
        :host mat-table {
           display: flex;
           flex-direction: column;
           min-width: 100px;
           max-width: 800px;
           margin: 0 auto;
        }
    `],
    template:
    `<mat-card>
        <mat-card-title>Entity List 
        <button mat-button [routerLink]="['/create/entity']">
            CREATE
        </button>
        </mat-card-title>
        <mat-card-content>
            <mat-table #table matSort [dataSource]="entitiesDataSource" matSort class="mat-elevation-z2">
                <ng-container matColumnDef="id">
                    <mat-header-cell *matHeaderCellDef mat-sort-header> Id </mat-header-cell>
                    <mat-cell *matCellDef="let element"> {{element.id}} </mat-cell>
                </ng-container>
                <ng-container matColumnDef="name">
                    <mat-header-cell *matHeaderCellDef  mat-sort-header> Name </mat-header-cell>
                    <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
                </ng-container>
                <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
                <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
            </mat-table>
        </mat-card-content>
        <mat-card-content>
            <mat-paginator #paginator [length]="resultsLength"
                [pageSize]="5"
                [pageSizeOptions]="[5, 10, 20]">
            </mat-paginator>
        </mat-card-content>
    </mat-card>
    `
})
export class EntityLatestPageComponent implements AfterViewInit {

    private entities: EntityJson[];
    private entitiesDataSource: MatTableDataSource<EntityJson> = new MatTableDataSource();
    private displayedColumns = ['id', 'name'];

    resultsLength = 0;
    isLoadingResults = false;
    isRateLimitReached = false;

    @ViewChild(MatPaginator) paginator: MatPaginator;
    @ViewChild(MatSort) sort: MatSort;

    public constructor( @Inject(EntityService) private entityService: EntityService) {
    }

    public ngAfterViewInit() {

        // If the user changes the sort order, reset back to the first page.
        this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
        merge(this.sort.sortChange, this.paginator.page)
        .pipe(
            startWith({}),
            switchMap(() => {
            this.isLoadingResults = true;
            return this.entityService.fetchLatest(this.sort.active, this.sort.direction, 
                  this.paginator.pageIndex + 1, this.paginator.pageSize, 
                  (total) =>  this.resultsLength = total);
            }),
            map(data => {
            this.isLoadingResults = false;
            this.isRateLimitReached = false;
            //alternatively to response headers;
            //this.resultsLength = data.total;
            return data;
            }),
            catchError(() => {
            this.isLoadingResults = false;
            this.isRateLimitReached = true;
            return observableOf([]);
            })
        ).subscribe(data => this.entitiesDataSource.data = data);
    } 
}

Service:

import { EntityJson } from './entity.json';
import { ApiHelper } from '../common/api.helper';
import { Http, Headers, Response, RequestOptions } from '@angular/http';
import { Inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { AuthenticationService } from '../auth/authentication.service';
import { stringify } from 'query-string';

@Injectable()
export class EntityService {

    private options: RequestOptions;
    private apiPrefix: string;
    private apiEndpoint: string;

    constructor(
        @Inject(Http) private http: Http,
        @Inject(AuthenticationService) private authService: AuthenticationService) {

        this.options = authService.prepareRequestHeaders();
        this.apiPrefix = 'http://localhost:4200/api/v1/';
        this.apiEndpoint = this.apiPrefix + 'entities';
    }

    public fetchLatest(sort: string = '', order: string = '', page: number = 1, perPage: number = 5, initTotal: Function = () => {}): Observable<EntityJson[]> {
        return this.http.get(this.apiEndpoint +'?' + EntityService.createUrlQuery({sort: {field: sort, order: order}, pagination: { page, perPage }}), this.options)
            .map((res) => {
                const total = res.headers.get('x-total-count').split('/').pop();
                initTotal(total);
                return JSON.parse(res.text()).content
            });
    }

    //should be put in a util
    static createUrlQuery(params: any) {
        if (!params) {
            return "";
        }

        let page;
        let perPage;
        let field;
        let order;
        let query: any = {};
        if (params.pagination) {
             page = params.pagination.page;
             perPage =  params.pagination.perPage;
             query.range = JSON.stringify([
                page,
                perPage,
            ]);
        }
        if (params.sort) {
            field = params.sort.field;
            order = params.sort.order;
            if (field && order) {
                query.sort = JSON.stringify([field, order]);
            }
            else {
                query.sort = JSON.stringify(['id', 'ASC']);
            }
        }
        if (!params.filter) {
            params.filter = {};
        }
        if (Array.isArray(params.ids)) {
            params.filter.id = params.ids;
        }

        if (params.filter) {
            query.filter = JSON.stringify(params.filter)
        }
        console.log(query, stringify(query));
        return stringify(query);
    }
}

Spring Boot Rest Controller Endpoint

@GetMapping("entities")
public Iterable<Entity> filterBy(
        @RequestParam(required = false, name = "filter") String filterStr,
        @RequestParam(required = false, name = "range") String rangeStr, @RequestParam(required = false, name="sort") String sortStr) {
    //my own helpers - for source: https://github.com/zifnab87/react-admin-java-rest
    //FilterWrapper wrapper = filterService.extractFilterWrapper(filterStr, rangeStr, sortStr);
    //return filterService.filterBy(wrapper, repo);
}

Some notes:

  1. Make sure you import Modules: MatTableModule, MatPaginatorModule and MatSortModule along other modules from Material Design.
  2. I decided to populate resultsLength (total) from Response-Header x-total-count which I populate through Spring Boot @ControllerAdvice. Alternatively you can get this information from the object returned from EntityService (e.g Page for Spring Boot) although that implies you will need to use any as return type or declare wrapper class objects for all of the entities in your project if you want to be "type-safe".
ccpizza
  • 28,968
  • 18
  • 162
  • 169
Michail Michailidis
  • 11,792
  • 6
  • 63
  • 106
  • 1
    Hi. How do you link paginator and datasource in your example? – Sergiy Apr 01 '18 at 08:42
  • 1
    Hi! resultsLength contains the total number of elements. For my case I send this information using a ControllerAdvice and I add a header that contains the total number of elements matching a JPA repositoty query. Then based on the pageSize paginator is calculating the number of pages needed. Check the arrow function I am passing in fetchLatest(). Hope this helps. – Michail Michailidis Apr 01 '18 at 13:45
  • 1
    Sergey, I think the point is that they're *not* linked at all. Just indirectly by providing events that update the data source and reading the total resultsLength. – infogulch May 10 '18 at 23:22
  • 1
    @Michali , can you please explain the EntityLatestPageComponent class logic with respect to fetching data from back end ? The logic inside ngAfterViewInit method of this class is very difficult to understand – Atul Oct 09 '18 at 06:11
  • 1
    @Atul you can have a look at my github repository here: https://github.com/zifnab87/react-admin-java-rest/tree/master/src/main/java/reactAdmin/rest it is an implementation to support the REST endpoint conventions of admin-on-rest/react-admin default data-provider: https://marmelab.com/react-admin/DataProviders.html#example-response-processing - as for ngAfterViewInit I just extended it from answer: https://stackoverflow.com/a/47994113/986160 – Michail Michailidis Oct 09 '18 at 08:25
  • @Atul More specifically https://github.com/zifnab87/react-admin-java-rest/blob/master/src/main/java/reactAdmin/rest/services/FilterService.java – Michail Michailidis Oct 09 '18 at 08:32
  • @Michali : Thanks a lot for your repply . I will go through these and let you know if any queries . Is there any way we can have chat over any messenger ? – Atul Oct 09 '18 at 14:07
  • @Michali: If I comment out startWith({}) , the switchMap doesn't get called and eventually subscribe method doesn't get called. No data is returned – Atul Oct 15 '18 at 12:05
  • @Atul not sure how startWith() works but from what I understand is since you are piping multiple transformations of the data you need to start with an empty object – Michail Michailidis Oct 15 '18 at 19:21
  • 1
    to link the paginator to make new calls use (page)=" getServerData($event)" – Frizz1977 Jul 13 '20 at 07:21
  • Why would I want to use MatTableDataSource here instead of a plain array? What's the benefit? – atiyar Jan 07 '21 at 22:56
14

I figured out this problem following Table retrieving data through HTTP from angular material docs.

What the example says is, use ngAfterViewInit() plus observables to handle everything on the table, pagination, sorting and other stuff that you need, code:

import {Component, AfterViewInit, ViewChild} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material';
import {Observable} from 'rxjs/Observable';
import {merge} from 'rxjs/observable/merge';
import {of as observableOf} from 'rxjs/observable/of';
import {catchError} from 'rxjs/operators/catchError';
import {map} from 'rxjs/operators/map';
import {startWith} from 'rxjs/operators/startWith';
import {switchMap} from 'rxjs/operators/switchMap';

/**
 * @title Table retrieving data through HTTP
 */
@Component({
  selector: 'table-http-example',
  styleUrls: ['table-http-example.css'],
  templateUrl: 'table-http-example.html',
})
export class TableHttpExample implements AfterViewInit {
  displayedColumns = ['created', 'state', 'number', 'title'];
  exampleDatabase: ExampleHttpDao | null;
  dataSource = new MatTableDataSource();

  resultsLength = 0;
  isLoadingResults = false;
  isRateLimitReached = false;

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  constructor(private http: HttpClient) {}

  ngAfterViewInit() {
    this.exampleDatabase = new ExampleHttpDao(this.http);

    // If the user changes the sort order, reset back to the first page.
    this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);

    merge(this.sort.sortChange, this.paginator.page)
      .pipe(
        startWith({}),
        switchMap(() => {
          this.isLoadingResults = true;
          return this.exampleDatabase!.getRepoIssues(
            this.sort.active, this.sort.direction, this.paginator.pageIndex);
        }),
        map(data => {
          // Flip flag to show that loading has finished.
          this.isLoadingResults = false;
          this.isRateLimitReached = false;
          this.resultsLength = data.total_count;

          return data.items;
        }),
        catchError(() => {
          this.isLoadingResults = false;
          // Catch if the GitHub API has reached its rate limit. Return empty data.
          this.isRateLimitReached = true;
          return observableOf([]);
        })
      ).subscribe(data => this.dataSource.data = data);
  }
}

export interface GithubApi {
  items: GithubIssue[];
  total_count: number;
}

export interface GithubIssue {
  created_at: string;
  number: string;
  state: string;
  title: string;
}

/** An example database that the data source uses to retrieve data for the table. */
export class ExampleHttpDao {
  constructor(private http: HttpClient) {}

  getRepoIssues(sort: string, order: string, page: number): Observable<GithubApi> {
    const href = 'https://api.github.com/search/issues';
    const requestUrl =
        `${href}?q=repo:angular/material2&sort=${sort}&order=${order}&page=${page + 1}`;

    return this.http.get<GithubApi>(requestUrl);
  }
}

Look that everything is handled inside the ngAfterViewInit thanks to observables. the line this.resultsLength = data.total_count; is expecting that your service is returning the data with total register counts, in my case I'm using springboot and its returned everything that I need.

If you need more clarification, write any comment and I gonna update the answer, but checking the example from the docs you'll figure it.

Wilfredo P
  • 4,070
  • 28
  • 46
  • 1
    I'm trying to use paginator with server side pagination too, how do you manage the next page and previous page events? thnx – bre Jan 21 '18 at 14:27
  • 1
    @bre How you are subscribing the event to the control it will detects when any event will fire and refresh the data. – Wilfredo P Jan 21 '18 at 17:45
  • 1
    hi thank for quick replay but i ended using ngxdatatable :-) – bre Feb 03 '18 at 06:53
  • 1
    @Wilfredo: Can you please share the Rest Controller implementation? I'm facing issues to integrate it with Rest Controller. – Maverick Apr 09 '19 at 05:32
  • 1
    @Wilfredo: I figured out and integrated with Rest Controller. Thanks – Maverick Apr 10 '19 at 11:39
  • What about building a reusable table based on this table? –  Oct 17 '20 at 15:13
  • Why would I want to use `MatTableDataSource` here instead of a plain array? What's the benefit? – atiyar Jan 07 '21 at 22:50
  • @atiyar Although I have about a year and half without working with angular, I recall that it was because I was using Material, so, for a Mat table as far as I concerned it's needed https://material.angular.io/components/table/overview but yes you can use a simple array. – Wilfredo P Jan 08 '21 at 12:52
4

This is a combination of Michail Michailidis's answer along with the official table pagination example, condensed down into a single file, and using a mock "network" service class that returns an Observable and simulates latency.

If you have a Material 2 + Angular 5 project up and running, you should be able to drop this into a new component file, add it to your modules list, and start hacking. At least it should be a lower barrier to entry for getting started.

import { ViewChild, Component, Inject, OnInit, AfterViewInit } from '@angular/core';
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { merge } from 'rxjs/observable/merge';
import { of as observableOf } from 'rxjs/observable/of';
import { catchError } from 'rxjs/operators/catchError';
import { map } from 'rxjs/operators/map';
import { startWith } from 'rxjs/operators/startWith';
import { switchMap } from 'rxjs/operators/switchMap';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Component({
  selector: 'app-element-table',
  styles: [`
    :host mat-table {
      display: flex;
      flex-direction: column;
      min-width: 100px;
      max-width: 800px;
      margin: 0 auto;
    }
  `],
  template: `
  <mat-card>
    <mat-card-title>Element List</mat-card-title>
    <mat-card-content>
      <mat-table #table matSort [dataSource]="elementDataSource" class="mat-elevation-z2">

        <!-- Position Column -->
        <ng-container matColumnDef="position">
         <mat-header-cell *matHeaderCellDef mat-sort-header> No. </mat-header-cell>
         <mat-cell *matCellDef="let element"> {{element.position}} </mat-cell>
        </ng-container>

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

        <!-- Weight Column -->
        <ng-container matColumnDef="weight">
         <mat-header-cell *matHeaderCellDef mat-sort-header> Weight </mat-header-cell>
         <mat-cell *matCellDef="let element"> {{element.weight}} </mat-cell>
        </ng-container>

        <!-- Symbol Column -->
        <ng-container matColumnDef="symbol">
         <mat-header-cell *matHeaderCellDef mat-sort-header> Symbol </mat-header-cell>
         <mat-cell *matCellDef="let element"> {{element.symbol}} </mat-cell>
        </ng-container>

        <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
        <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
      </mat-table>
    </mat-card-content>
    <mat-card-content>
      <mat-paginator #paginator [length]="resultsLength"
        [pageSize]="5"
        [pageSizeOptions]="[5, 10, 20]"
        showFirstLastButtons>
      </mat-paginator>
    </mat-card-content>
  </mat-card>
  `
})
export class ElementTableComponent implements AfterViewInit {
  public elementDataSource = new MatTableDataSource<PeriodicElement>();
  public displayedColumns = ['position', 'name', 'weight', 'symbol'];

  private entities: PeriodicElement[];
  private elementService = new ElementService();

  resultsLength = 0;
  isLoadingResults = false;
  isRateLimitReached = false;

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  public constructor() {
  }

  public ngAfterViewInit() {
    this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
    merge(this.sort.sortChange, this.paginator.page)
      .pipe(
        startWith({ data: [], resultsLength: 0 } as ElementResult),
        switchMap(() => {
          this.isLoadingResults = true;
          return this.elementService.fetchLatest(
            this.sort.active, this.sort.direction,
            this.paginator.pageIndex + 1, this.paginator.pageSize);
        }),
        map(result => {
          this.isLoadingResults = false;
          this.isRateLimitReached = false;
          this.resultsLength = result.resultsLength;
          return result.data;
        }),
        catchError(() => {
          this.isLoadingResults = false;
          this.isRateLimitReached = true;
          return observableOf([]);
        })
      ).subscribe(data => this.elementDataSource.data = data);
  }
}

// Simulates server-side rendering
class ElementService {
  constructor() { }

  fetchLatest(active: string, direction: string, pageIndex: number, pageSize: number): Observable<ElementResult> {

    active = active || 'position';
    const cmp = (a, b) => (a[active] < b[active] ? -1 : 1);
    const rev = (a, b) => cmp(b, a);
    const [l, r] = [(pageIndex - 1) * pageSize, pageIndex * pageSize];

    const data = [...ELEMENT_DATA]
      .sort(direction === 'desc' ? rev : cmp)
      .filter((_, i) => l <= i && i < r);

    // 1 second delay to simulate network request delay
    return new BehaviorSubject({ resultsLength: ELEMENT_DATA.length, data }).debounceTime(1000);
  }
}

interface ElementResult {
  resultsLength: number;
  data: PeriodicElement[];
}

export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
  { position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
  { position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
  { position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
  { position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
  { position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
  { position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
  { position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
  { position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
  { position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
  { position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
  { position: 11, name: 'Sodium', weight: 22.9897, symbol: 'Na' },
  { position: 12, name: 'Magnesium', weight: 24.305, symbol: 'Mg' },
  { position: 13, name: 'Aluminum', weight: 26.9815, symbol: 'Al' },
  { position: 14, name: 'Silicon', weight: 28.0855, symbol: 'Si' },
  { position: 15, name: 'Phosphorus', weight: 30.9738, symbol: 'P' },
  { position: 16, name: 'Sulfur', weight: 32.065, symbol: 'S' },
  { position: 17, name: 'Chlorine', weight: 35.453, symbol: 'Cl' },
  { position: 18, name: 'Argon', weight: 39.948, symbol: 'Ar' },
  { position: 19, name: 'Potassium', weight: 39.0983, symbol: 'K' },
  { position: 20, name: 'Calcium', weight: 40.078, symbol: 'Ca' },
];

Btw, this issue on material2 about filtering might be useful if you're looking to implement filtering yourself.

infogulch
  • 1,182
  • 10
  • 22
  • 1
    I get the error: Type '{} | PeriodicElement[]' is not assignable to type 'PeriodicElement[]'. Type '{}' is not assignable to type 'PeriodicElement[]'. I linie 102: ).subscribe(data => this.elementDataSource.data = data); any idea? – Tony Jul 05 '18 at 02:20
  • 1
    The only thing I can think of is the type unification rules changed, and maybe 'return observableOf([]);' inside catchError doesn't cleanly unify with PeriodicElement[] anymore. Try: 'return observableOf([] as PeriodicElement[]);' – infogulch Jul 06 '18 at 02:13