Search code examples
angularrxjsmat-table

Displaying Paginated Data in Mat-Table (Angular)


I have a CRUD REST API which sends paginated data to the client. Consider the following data interface:

export interface LCKData {
    // Some basic type fields from DB
}

export interface LCKPageData {
    content: LCKData[],
    pageable: any,
    totalElements: number,
    totalPages: number,
    last: boolean,
    numberOfElements: number,
    size: number,
    number: number,
    sort: any,
    first: boolean,
    empty: boolean
}

The second interface is the standard paginated data from Spring Boot web application. I use this structure to set the Paginator component properties, i.e. number of total pages.

I use these two services to fetch data and create a DataSource:

let LCKBaseAddress: string = "...";

@Injectable()
export class LockService {

    constructor(private http: HttpClient) { }

    findLCK(sortColumn = "sysTime", sortOrder = 'desc',
        pageNumber = 0, pageSize = 10): Observable<LCKPageData> {
        return this.http.get(LCKBaseAddress, {
            params: new HttpParams()
                .set('q', sortColumn)
                .set('d', sortOrder)
                .set('p', pageNumber.toString())
                .set('s', pageSize.toString())
        }).pipe(map(res => {
            res['payload'] = res;
            return res["payload"];
        }))
    };
   
}

And:

export class MainService extends DataSource<LCKData>{

    private loadingSubject = new BehaviorSubject<boolean>(false);
    private pageSubject = new BehaviorSubject<LCKData[]>([]);
    private locks$ = new Observable<LCKPageData>();
   
    constructor(private lockService: LockService) {
        super();
    };

    connect(collectionViewer: CollectionViewer): Observable<LCKData[]> {
        return this.locks$.pipe(pluck("content"));
    }

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

    loadLCK(sortColumn = "sys_time", sortOrder = 'desc',
        pageNumber = 0, pageSize = 10) {
        this.lockService.findLCK(sortColumn, sortOrder, pageNumber, pageSize).pipe(
            catchError(() => of([])),
            finalize(() => this.loadingSubject.next(false))
        )
            .subscribe(lcks => this.pageSubject.next(lcks['content']));
    }
}

And finally the table component:

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.css']
})
export class TableComponent implements OnInit {

  public columns: string[];

  dataSource: MainService;

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

  constructor(public lock: LockService) {}

  ngOnInit(): void {
    this.dataSource = new MainService(this.lock);
    this.dataSource.loadLCK();
    this.columns = [ // Some columns //];
  }

  ngAfterViewInit() {
    this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
    merge(this.sort.sortChange, this.paginator.page)
      .pipe(
        tap(() => this.loadLessonsPage())
      )
      .subscribe();
  }

  loadLessonsPage() {
    this.dataSource.loadLCK(
      'sysTime',
      this.sort.direction,
      this.paginator.pageIndex,
      this.paginator.pageSize);
  }

}

And the Template:

<table mat-table [dataSource]="dataSource" class="mat-elevation-z2" matSort matSortActive="sysTime"
        matSortDirection="desc" matSortDisableClear>
// Column and row definition

The code runs without any errors but nothing is displayed in the table.

Previously I have tested a same approach with non-paginated data and everything was OK, where the server responded List<LCKData> instead of LCKPageData.

I use Angular 10.

Any ideas?


Solution

  • Your connect() method returns an observable that never emits!

    Instead of initializing locks$ to be a new observable, you should define it off of your pageSubject.

    private locks$: Observable<LCKData[]> = this.pageSubject.asObservable();
    

    No need to use pluck since you're already emitting just the content property into the subject (this.pageSubject.next(lcks['content'])):

    connect(collectionViewer: CollectionViewer): Observable<LCKData[]> {
       return this.locks$;
    }