I'm trying to implement HTTP pagination as exemplified on documents with my own API.
The problem is that the total items count (length) is being set incorrectly!
My code follows exactly the same thing of the example.
It should just respect the value that I set. On my code, I use my API response (total) to set the paginators length, just as on the docs example.
Looks like the length "blinks" from the total given by API to the current page length.
For example, if I load a resource that have a total of 9 items, and the page size is 5,
imediatelly after it load, I can see (very fast) the total of 9, with the next button enabled, then
it goes to 5 (the page length, not the total).
If in the same same, I set page size to 10, it shows the total of 9 (right, because the page 1 have exactly 9).
Looks like this is a behavior from non http pagination, where it try to paginate from the entire dataset.
Cannot provide a plunker right now, but the relevant code is:
ngAfterViewInit() {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
merge(this.sort.sortChange, this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.modelProvider.list({page: this.paginator.pageIndex + 1, size: this.paginator.pageSize});
}),
map((collection: Collection<any>) => {
this.isLoadingResults = false;
this.resultsLength = collection.meta.total;
return collection.data;
}),
catchError(() => {
this.isLoadingResults = false;
return observableOf([]);
})
).subscribe(data => this.dataSource.data = data );
}
ngAfterViewInit() {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
merge(this.sort.sortChange, this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.modelProvider.list({page: this.paginator.pageIndex + 1, size: this.paginator.pageSize});
}),
map((collection: Collection<any>) => {
this.isLoadingResults = false;
this.resultsLength = collection.meta.total;
return collection.data;
}),
catchError(() => {
this.isLoadingResults = false;
return observableOf([]);
})
).subscribe(data => this.dataSource.data = data );
}
And my paginator on view is defined:
<mat-paginator #paginator
[length]="resultsLength"
[pageSize]="5"
[pageSizeOptions]="[5, 10, 20]"
*ngIf="paginate">
</mat-paginator>
The latest available versions, I've just installed everything.
Not sure, but looks like the problem occurs here: (see line 223) https://github.com/angular/material2/blob/2b2eb185af1b67b04d66a49085d51772b58bc365/src/lib/table/table-data-source.ts#L219-L231
Since the data is being paginated on backend, it should not be filtered on front-end (or if so, it should not change the totals, since it will be filtering only the current page)
There must be something with your setup. I have an app that I use for testing and it's working just fine (backend in java):
@julianobrasil I've checked many times my setup, didn't found anything wrong.
Following the table-data-source.ts
logic:
The _updateChangeSubscription()
method watches for data
, filter
, sort
or page
changes, then trigger the following map methods: _filterData()
, _orderData()
and _pageData()
.
IMHO, the problem is that _filterData()
don't cares about if I'm loading the data asynchronously, and overrides the length provided by my code using the length
input on the paginator component.
Not sure if it's related, but the StackBlitz on the docs simply don't work: https://stackblitz.com/angular/gokmekyjoxr
Found the reason.
In some doc example, I've found that I need to put this inside ngAfterViewInit
.
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
On the http example, this lines doesn't exists. That's the reason. Since the pagination isn't controlled by the table itself, the paginator should not be associated to it (neither the sort).
I think that the docs should be clearer about this. Unfortunately I'm on vacation now and can't send a PR with a fix for the docs. I'll love if someone do it.
Do you mean the example below? If the answer is yes... well, the example itself is working as expected. In this case the paginator is not bound to the datasource at all: this.paginator.page
observable is used to fire the function that grabs the desired data from the server and, after that, set datasource.data
:
ts
this.datasource.data = dataReceivedFromServer
Can you re-open with a reproduction of your issue in stackblitz so we can investigate the issue?
I have to agree, I just went through the same experience / confusion as @iget-master with my own attempt to follow the examples. Taking this code out of ngAfterViewInit made it behave as I expected it to.
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
The docs could certainly explain this more clearly in the context of the HTTP example, imho... it's quite easy to overlook that difference among the examples.
@andrewseguin When i move to next page and make a http call, paginator length is changed to length of table datasource.data
i am just updating the totalLength only if it's 0 .
if(this.totalLength === 0){
this.totalLength = data.total;
}
<mat-paginator #paginator [length]="totalLength"
[pageSize]="limit"
[pageSizeOptions]="pageLimit"
(page)="changePage($event)">
</mat-paginator>
@narendrasinghrathore, do you think you'd be able to reproduce it using a stackblitz demo? I've never faced this issue in any projects of mine.
@julianobrasil i can share my teamviewer to show you.
It's hard to setup with same scenario
Not today. I have an appointment at the ophthalmologist right now and probably I won't be able to use a computer for the rest of the day after that.
@julianobrasil No issues, i will try reproduce online if possible or else please help out on teamviewer if you can.
It solves when I use a flag to *ngIf the page and toggle before and after the rest call executes. I showed a spinner when *ngIf is false.
TS :
```
this.service
.get('pools/calculations',{ pageNumber: page?page:this.page, pageSize: pSize?pSize:this.pSize })
.then(data => {
this.isLoadingResults=false;
const ELEMENT_DATA =data.searchResults;
this.dataSource = new MatTableDataSource
this.dataSource.paginator = this.paginator;
if (data.pagingInfo && typeof data.pagingInfo == "object") {
this.pageSize = data.pagingInfo.pageSize;
this.listLength = data.pagingInfo.totalRows;
this.pageNumber = data.pagingInfo.pageNumber;
}
})
HTML:
```
But if I remove the ngIf the problem occours.
I have worked around this issue by overriding MatTableDataSource
, and the _updatePaginator
method to accept data from a custom paginated query object similar to the following:
export class HttpPaginatedDataSource<T> extends MatTableDataSource<T> {
/**
* Custom dto containing paginated response data from a http request
*/
private readonly paginatedQuery: {
data$: Observable<Array<T>>;
totalElements: number;
// ... other metadata
};
// ... logic for data binding
/**
* Override update paginator method
* to ensure total unfiltered element count is consistent with the http result
*/
public _updatePaginator(filteredDataLength: number): void {
if (this.filter === '') {
super._updatePaginator(this.paginatedQuery.totalElements);
} else {
super._updatePaginator(filteredDataLength);
}
}
}
I'm of the opinion the pager sound not behave like this. It "should just work".
Just ran into a version of this... my issue was when navigating away from the component containing the mat-table and navigating back, the length attribute was set to the number of records currently displayed... ie 10, 25, 50
I stored the length in a local variable after it is retrieved from the server side API.
Adding the following to my ngAfterViewInit restored the length value in the paginator when the user navigates back to the component.
ngAfterViewInit(){
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
setTimout(()=>this.paginator.length = this._localLengthVariable)
}
This a real issue. If we assign the pagination length with a number greater than the previously assigned value its working, but he logic fails.
Hey all. I've seen this bug for server side pagination.
The problem appears to be that when you are connecting a paginator to the MatTableDataSource, you are providing a way for the paginator to update the length property based on that data source. If you are also providing length as well, that length value will be overwritten when the data source gets updated.
A solution is to not connect the paginator to the MatTableDataSource. You do not need an ngIf*. If you do not connect the paginator to the datasource the only source that the paginator uses to update are the properties you explicitly provide.
So your paginator will look something like this
<mat-paginator
[length]="total$ | async"
[pageSize]="pageSize$ | async"
[pageSizeOptions]="pageSizeOptions"
[pageIndex]="currentPage$ | async"
(page)="onPaginateChange($event)">
</mat-paginator>
And your component will not have any reference to paginator or MatTableDataSource.
Note: In my use case, I'm using paginator without using a MatTable and instead iterating on a custom component. If you are using MatTable then simply don't connect the paginator to the datasource and nix this code: this.dataSource.paginator = this.paginator
Hey all. I've seen this bug for server side pagination.
The problem appears to be that when you are connecting a paginator to the MatTableDataSource, you are providing a way for the paginator to update the length property based on that data source. If you are also providing length as well, that length value will be overwritten when the data source gets updated.
A solution is to not connect the paginator to the MatTableDataSource. You do not need an ngIf*. If you do not connect the paginator to the datasource the only source that the paginator uses to update are the properties you explicitly provide.
So your paginator will look something like this
<mat-paginator [length]="total$ | async" [pageSize]="pageSize$ | async" [pageSizeOptions]="pageSizeOptions" [pageIndex]="currentPage$ | async" (page)="onPaginateChange($event)"> </mat-paginator>
And your component will not have any reference to paginator or MatTableDataSource.
Note: In my use case, I'm using paginator without using a MatTable and instead iterating on a custom component. If you are using MatTable then simply don't connect the paginator to the datasource and nix this code:
this.dataSource.paginator = this.paginator
This solved my issue. I had no idea that assigning the paginator to the data source would overwrite the length I was providing. You would think it would be the other way around.
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
_This action has been performed automatically by a bot._
Most helpful comment
@andrewseguin When i move to next page and make a http call, paginator length is changed to length of table datasource.data
i am just updating the totalLength only if it's 0 .
if(this.totalLength === 0){ this.totalLength = data.total; }
<mat-paginator #paginator [length]="totalLength" [pageSize]="limit" [pageSizeOptions]="pageLimit" (page)="changePage($event)"> </mat-paginator>