4
votes

I have an Angular Material Data Table and I am trying to add pagination to it.

I have come across this problem that if the data is an async source then Mat-Paginator doesn't work.

I tried with Sync source and Pagination works as expected but as I have an observable the pagination does not get initialized when the data finally arrives.

However, I have tried searching for this problem and so far no one seems to be having this problem so it may be down to my setup.

Following is my ts file for my component.

import { Component, Input, Output, EventEmitter, style, Inject, OnInit, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { Country} from '../../core/models/country';
import { OnChanges, AfterViewInit } from '@angular/core';
import { MatTableDataSource, MatPaginator } from '@angular/material';

@Component({
    // tslint:disable-next-line:component-selector
    selector: 'app-country-list',
    templateUrl: './country-preview-list.html',
    styleUrls : ['./country-preview-list.css']
})
export class CountryListComponent implements OnChanges, AfterViewInit {
    @Input() public countries: Country[];
    displayedColumns = ['Name', 'Code'];
    dataSource: MatTableDataSource<Country>;

    @ViewChild(MatPaginator) paginator: MatPaginator;

    ngOnChanges(): void {
        this.dataSource = new MatTableDataSource<Country>(this.countries);
    }

    ngAfterViewInit() {
        this.dataSource.paginator = this.paginator;
      }

}

and Parent component html has this line to add my component

<app-country-list [countries]="countries$ | async"></app-country-list>

Edit

Parent Component TS file

import { Component, ChangeDetectionStrategy, OnChanges } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { take } from 'rxjs/operators';

import * as fromCountries from '../../state/countries/reducers';
import * as fromConfiguration from '../../state/configuration/reducers';
import * as country from '../../state/countries/actions/countries';
import * as configuration from '../../state/configuration/actions/configuration';
import { Country} from '../../core/models/country';
import { OnInit } from '@angular/core/src/metadata/lifecycle_hooks';

@Component({
  selector: 'app-countries-page',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <app-country-search (searchCountries)="search($event)"></app-country-search>
    <app-country-list [countries]="countries$ | async"></app-country-list>
  `,
})
export class CountriesPageComponent {
  countries$: Observable<Country[]>;

  constructor(private store: Store<fromCountries.State>) {
    this.countries$ = store.pipe(select(fromCountries.getAllCountries));
  }

  search(query: any) {
    this.store.dispatch(new country.Search(query));
  }

}

if change countries$ | async to a synchronous list of countries; paginator works.

Can someone please help. Thanks

2
Based on the code you have provided, there is no Observable (of any type) named countries$ in the component. Is this all the code you have? - R. Richards
Have you tried moving this.dataSource.paginator = this.paginator; to the end of ngOnChanges() ? Since you create a new datasource, its paginator property is reset. You could also try simply setting the datasource's data (using this.dataSource.data) directly. - Jeto
@R. Richards I'm guessing countries$ is defined in the parent component's script. Having the complete code (or even better, a StackBlitz) would definitely help though. - Jeto
@Jeto You're right! That is where that would live. Agreed, we need to see more. - R. Richards
I have found out why it is acting as if paginator is disconnected. I tried to log it to console and it comes back as undefined. Your idea about StackBlitz was good as html code was hidden. I have the data table and paginator inside an ngIf and when the ViewChild tries to read paginator; it is not there so it remains undefined. Need to figure out how to keep it hidden until data arrives but at the same time - don't let it be undefined. - Robert Dinaro

2 Answers

8
votes

Your last comment help me find the solution to this. This is indeed caused by the fact that the dataTable is inside a ngIf*.

The issue is described here in more detail.

To solve the problem remove this code:

@ViewChild(MatPaginator) paginator: MatPaginator;

ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
}

and add this:

private paginator: MatPaginator;

@ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) {
  this.paginator = mp;
  this.dataSource.paginator = this.paginator;
}
0
votes

In case you need a loading(async) then after loading you need to render the table and set the paginate async. try this

component.html

<loading-component *ngIf="isLoading$ | async"></loading-component> <--WHILE Loading

<div *ngIf="!(isLoading$ | async)"> <-- AFTER Loading
    <ng-container *ngIf="dataSource.data.length >= 1; else noData">  <--Check if there's data
        <table mat-table [dataSource]="dataSource">
            ...
        </table>
 <mat-paginator></mat-paginator>
    </ng-container>
    <ng-template #noData>  <- In case there is not data (to not show the table/table-header)
        ...
    </ng-template>
</div>

component.ts

import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { Observable } from 'rxjs/Observable';

export class Component {
  dataSource = new MatTableDataSource<any>();
  isLoading$: Observable<boolean>;  
  private _paginator: MatPaginator;
  
@ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) { <--SET you can fetch (async)
    this._paginator = mp;
    this.dataSource.paginator = this._paginator;
  }
  .. the rest of the component logic ...

With the SET you can fetch async data to table and then paginate it.

Keep coding \m/