角度 MatPaginator 没有被初始化

我有两个组成部分。两者都有地毯表和分页符,并且分页是为一个组件工作的,而不是为另一个组件工作的,尽管代码是相似的。下面是我的 html:

<div class="mat-elevation-z8">
<mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="col1">
<mat-header-cell *matHeaderCellDef mat-sort-header> Column1 </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.col1}} </mat-cell>
</ng-container>


<ng-container matColumnDef="col2">
<mat-header-cell *matHeaderCellDef mat-sort-header> Column2 </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.col2}} </mat-cell>
</ng-container>


<!-- Different columns goes here -->


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


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

下面是我在 Component.ts 中的代码:

dataSource: MatTableDataSource<any>;
displayedColumns = ['col1', 'col2', ... ];


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


ngOnInit(): void {
// Load data
this.dataSource = new MatTableDataSource(somearray);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}

类似的代码正在为其他组件工作,表正在使用分页正确地呈现,不知道这段代码出了什么问题。

如果你能帮忙,我将不胜感激

125449 次浏览

I resolved a similar issue by surrounding the instantiation with a timeout. Try this :

setTimeout(() => this.dataSource.paginator = this.paginator);

Although selected answer works and solves the problem it is still a workaround. This is proper and more elegant way to deal with the problem.

Try adding AfterViewInit interface to your class, then put this.dataSource.paginator = this.paginator inside ngAfterViewInit() method

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

then you would not have to call workaround setTimeout.

In my case, the <mat-paginator> element was inside a container that had an *ngIf on it, which did not render until the data loaded asynchronously. This causes this.paginator to be undefined even in ngAfterViewInit. This causes it to silently fail as MatTableDataSource has no problem with you setting paginator to undefined.

The solution was to move the <mat-paginator> outside of the *ngIf'd container

Hopefully, this helps someone who is in the same situation as me.

To make it work I had to set the paginator after the data was fetched from source

getVariables() {
this.activeRoute.params.subscribe(params => {
if (params['id'] && (params['type'] === Type.CodeList)) {
this.dataService
.getItems(this.currentLanguage, params['id'])
.subscribe((items: any) => {
this.dataSource.data = this.items;
this.dataSource.paginator = this.paginator;
})
}
})
}
  <mat-paginator
#scheduledOrdersPaginator
(page)="pageEvent($event)">
</mat-paginator>
pageEvent(event){
//it will run everytime you change paginator
this.dataSource.paginator = this.paginator;
}

I am quite a beginner in angular and typescript, but after having the same problem (except, that for me sorting didn't work either), what helped was creating a function 'refreshDataScource()' and having it called from ngAfterViewInit() as well as after each time the server responds with new data. In this function I simply refresh the dataSource with the paginator and the sort. Like this:

refreshDataSource() {
this.dataSource = new MatTableDataSource(myDataArray);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}

It fixed the paginator and the sort. Everything works now perfectly. However I am not sure if it is just a workaround or a real fix.

Using setTimeOut() will solve the issue temporarily, however, this will again fail to work if you are pushing a huge load of Data [say 1000 rows] into MatDataSource.

we've found that the MatTable loads very slowly if a large data set is set before you set the data-source paginator.

ngOninit(){
// initialize dataSource here
}
ngAfterViewInit() {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;


/* now it's okay to set large data source... */
this.dataSource.data = [GetLargeDataSet];}

So, initialize the data-Source at first place and set the properties like "Paginator" or "Sort" before pushing the data into the source into the "Data" property.

Angular 7+ (8/9/10/11/12) There is another elegant solution to solve that problem.

Short Version

Immediately after setting data source invoke ChangeDetectorRef.detectChanges().

Long Version

Step1:

Import ChangeDetectorRef & Material related stuff

import { ..., ChangeDetectorRef } from '@angular/core';
import { MatSort, MatTableDataSource, MatPaginator } from '@angular/material';

Step2:

Set class-properties in your component

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

Step3:

Inject ChangeDetectorRef in your constructor

constructor (..., private cdr: ChangeDetectorRef, ...) { ... }

Step4:

Set data source and invoke detectChanges()

this.dataSource = new MatTableDataSource (MY_DATA);
this.cdr.detectChanges();

Optional Steps:

After that, you can set other properties like

this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;

A solution without using setTimeout is to use set :


@ViewChild(MatPaginator) paginator: MatPaginator;




set matPaginator(mp: MatPaginator) {
this.paginator = mp;
this.dataSource.paginator = this.paginator;
}

In my case, data coming from service asynchronously so neither could use ngOnInit or ngAfterViewInit, I used ngOnChanges, something like below:

ngOnChanges(change: SimpleChanges) {
if (change.properties) {
if (this.properties.length > 0) {
this.dataSource = new MatTableDataSource(this.properties);
this.dataSource.paginator = this.paginator;
}
}
}

Be sure to set the [DataSource] attribute at mat-table element at html to the data source property from the component so to ensure the data source get bind with the table and paginator.

This took me hours to finally track down and understand why my table wasn't working. Placing some console.logs() helped me figure out the order of events and why it didn't work consistently. My scenario was similar to the original issue above with a dynamic data source, but slightly different. For me, when I would first refresh my angular app, the paginator state would be set and then the data for my table would be set. When these events would happen in the this order the paginator worked just as expected.

Because I was using ReplaySubjects for getting the data for my tables, my paginator state would be set in ngAfterViewInit and then the table data would come in from my subscription (it depends on user ID so I don't have an initial value and that is why I didn't use BehaviorSubjects). My problem was that, when I would navigate to another component in my app, and return to my dynamic table data source, the table data would be set before the paginator state. This would make the paginator show page x, but the displayed data would always be the first page of data.

To fix this annoying issue I:

  1. Wrote a simple function for setting my table data as someone mentioned above:
  setTableData() {
// set table data
this.tableData.data = this.images.filter(img => this.filterData(img));


// some other code below
....
}
  1. Added two flags in my component, one for if I had loaded the table data and one for if my pagination state had been set. This way I could ensure that the pagination state is set before the data.
  // initialize our data source for our table and set flags for state
tableData = new MatTableDataSource<IImage>(this.images);
loading = true;
setPageState = false;
  1. Added a setTimeout to ngAfterViewInit which would set my paginator state and set my table data only if the table data came through before ngAfterViewInit was called. The setTimeout prevents the annoying "expression changed after value was checked" error.
  ngAfterViewInit() {
// add pagination state to our table
setTimeout(() => {
console.log('Set paginator state');
this.setPageState = true;
this.paginator.pageIndex = this.state.pageIndex;


// check if we are not loading data, meaning the table came in first so
// we need to set the data here
if (!this.loading) {
this.setTableData();
}
}, 0);
}
  1. Lastly, in ngOnInit, where I subscribe to my data, I do not set my table data unless the paginator state was set first:
  ngOnInit() {
console.log('ngOnInit');
this.tableData.sort = this.sort;
this.tableData.paginator = this.paginator;


// listen for data
this.dbService.images.subscribe(images => {
console.log('Data subscription');
// save images
this.images = images;


// only set table data if paginator state has been set, otherwise it doesnt work
if (this.setPageState) {
this.setTableData();
}


// hide spinner and update that we have data
this.loading = false;
});


// other code
.....
}

And with that, my pagination was finally working consistently for when I first login to the app and when I navigate to other pages and go back to my dynamic table.

The solution that I find is to to set the paginator after the data is successfully loaded.

this._empService.GetEmpList().subscribe(
data => {
this.empListDataSource = new MatTableDataSource<empToShow>(Object.values(data))
this.empListDataSource.paginator = this.paginator
}
)

Only change

dataSource: MatTableDataSource<any>;

for

dataSource = new MatTableDataSource();
dataSource = new MatTableDataSource();
displayedColumns = ['col1', 'col2', ... ];
@ViewChild('scheduledOrdersPaginator') paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
ngOnInit(): void {
// Load data
this.dataSource = new MatTableDataSource(somearray);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}

I had the same issue (table data displaying but MatPaginator is not working). In my case, I forgot to create "new MatTableDataSource"

this.dataSource = somearray;

not enable MatPaginator while this.dataSource = new MatTableDataSource(somearray); does.

extract from material documentation

"To simplify the use case of having a table that can sort, paginate, and filter an array of data, the Angular Material library comes with a MatTableDataSource that has already implemented the logic of determining what rows should be rendered according to the current table state."

hope this answer helps someone.

I also had this problem. I added reference scheduledOrdersPaginator into pagintaor tag and received with @ViewChild. It solved my pagination problem. Below code may help others.

dataSource = new MatTableDataSource();
displayedColumns = ['col1', 'col2', ... ];
@ViewChild('scheduledOrdersPaginator') paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
ngOnInit(): void {
// Load data
this.dataSource = new MatTableDataSource(somearray);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}

html:

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

Do check out this Stackoverflow Answer.

Setting the paginator in typescript after HTML components getting loaded (*ngIf) fixed the issue for me.

After trying all the above-provided solutions finally the simple solution worked for me. I am posting for reference if anyone stuck.

I am using Angular 8. Just add { static: false } in child reference

  @ViewChild(MatSort, { static: false }) sort: MatSort;
@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;

Got Solution From Here

I was using this :

@ViewChild(MatSort, { read: true, static: false }) sort: MatSort;

@ViewChild(MatPaginator, { read: true, static: false }) paginator: MatPaginator;

To paginate the table's data, add a <mat-paginator> after the table.

If you are using the MatTableDataSource for your table's data source, simply provide the MatPaginator to your data source. It will automatically listen for page changes made by the user and send the right paged data to the table.

Otherwise if you are implementing the logic to paginate your data, you will want to listen to the paginator's (page) output and pass the right slice of data to your table.

For more information on using and configuring the <mat-paginator>, check out the mat-paginator docs.

The MatPaginator is one provided solution to paginating your table's data, but it is not the only option. In fact, the table can work with any custom pagination UI or strategy since the MatTable and its interface is not tied to any one specific implementation.

@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
ngOnInit() {
this.dataSource.paginator = this.paginator;
}

See also https://material.angular.io/components/table/overview

Make sure that you have imported the MatPaginatorModule in the app.module.ts

None of the above solution worked for me.

I figured out what was the issue, mainly this.paginator will be undefined until the table is loaded and in view, that's why in some of the in places setTimeout solution works.

But in my case, my table was hidden behind some ngIf logic so the the table was only loaded after the ngIf becomes true(which happens on user interaction ) , but I was setting this.dataSource.paginator = this.paginator on ngOnInit

So, the solution depends on your case, the ground truth is make sure the table is loaded only then this.dataSource.paginator = this.paginator

I resolved it by, when user does the interaction and ngIf becomes true after that I call a function to set the paginator

     initPaginator(){
this.dataSource.paginator = this.paginator
}

Using setTimeout() is only a viable solution when you know how much time it will take to load the table. My problem was I using an *ngIf on the table (with !isLoading):

this.dataSource = new MatTableDataSource(this.rawData);


this.initPaginator();


this.isLoading = false;

The fix was setting my isLoading variable to false only after change detections and initializing the paginator:

this.dataSource = new MatTableDataSource(this.rawData);


this.isLoading = false;


this.cdr.detectChanges();


this.initPaginator();

So it loads the data -> shows the table -> detects changes -> inits paginator. I hope this helps anyone!

What worked for me was to do implement what Lonely suggested, regarding ChangeDetectorRef, and also, set an object with static: false, in the @ViewChild, like this:

  @ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;

for Angular version below 7, use read argument for MatPaginator.

@ViewChild(MatPaginator, {read: true}) paginator: MatPaginator;

this worked for me.

Note that this works for Dynamically loaded table data.

For me it was just add the property MatPaginator.length.

<mat-paginator [length]="Elements.length"
[pageSizeOptions]="[5, 10, 25, 100]">
</mat-paginator>
  private paginator: MatPaginator;
private sort: MatSort;


@ViewChild(MatSort) set matSort(ms: MatSort) {
this.sort = ms;
this.setDataSourceAttributes();
}


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


setDataSourceAttributes() {
if(this.dataSource !== undefined){
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
}

1st Solution

Move mat-paginator from inside *ngIf div to outside

2nd Solution

use static false while declaring MatPaginator or MatSort

@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
@ViewChild(MatSort, {static: false}) sort: MatSort;

I had a similar issue to this, with mat-paginator inside a container with a ngIf.

The only thing that worked for me was the comment:

Thank you - this was the solution for me. I opted to use [hidden] instead of ngIf so that the paginator would render even if there wasn't any data, but wouldn't show up to the user – TabsNotSpaces

To clarify, what I did was create a div, outside the container, with [hidden]=<negation_of_the_same_condition_as_the_ngIf>.

This is because of this.paginator is undefined at the time it going to assign to this.dataSource.paginator.

if you using static data this will work

 @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; // For pagination
@ViewChild(MatSort, {static: false}) sort: MatSort; // For Sort


ngOnInit(): void {
this.dataSource.data = this.dataList; // Data list is data array
}


ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator; // For pagination
this.dataSource.sort = this.sort; // For sort
}

if you using dynamic data (Data from API) this will work

For Pagination

  @ViewChild(MatPaginator, {static: false})
set paginator(value: MatPaginator) {
if (this.dataSource){
this.dataSource.paginator = value;
}
}

For Sort

  @ViewChild(MatSort, {static: false})
set sort(value: MatSort) {
if (this.dataSource){
this.dataSource.sort = value;
}
}

As a side note im using Angular 9 at the movement.

The question when the paginator is available in the view and can be retrieved and attached to the data source is the main crux of this issue and a common pitfall. Workarounds suggested here include using setTimeout() and ngAfterViewInit, are simply that - workarounds in the vein of "let's see how much we need to wait to make sure that the @ViewChild has set our component field with the correct paginator value".

The correct methodology is to attach the @ViewChild to a property setter and set the data source paginator as soon (and as often) as that setter is called with a valid paginator.

It is also very useful to have a single data source and not replace it every load (as I've seen many people do) - just bind a data source to the mat-table and update it's data field.

 <mat-table [dataSource]="dataSource" matSort>


<ng-container matColumnDef="col1">
<mat-header-cell *matHeaderCellDef mat-sort-header> Column1 </mat-header-cell>
<mat-cell *matCellDef="let row"> \{\{row.col1}} </mat-cell>
</ng-container>


<ng-container matColumnDef="col2">
<mat-header-cell *matHeaderCellDef mat-sort-header> Column2 </mat-header-cell>
<mat-cell *matCellDef="let row"> \{\{row.col2}} </mat-cell>
</ng-container>


<!-- additional columns go here -->


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


<mat-paginator #scheduledOrdersPaginator [pageSizeOptions]="[5, 10, 20]"></mat-paginator>
</div>
dataSource: MatTableDataSource<any> = new MatTableDataSource();
displayedColumns = ['col1', 'col2', ... ];


@ViewChild('scheduledOrdersPaginator') set paginator(pager:MatPaginator) {
if (pager) this.dataSource.paginator = pager;
}


@ViewChild(MatSort) set sort(sorter:MatSort) {
if (sorter) this.dataSource.sort = sorter;
}


ngOnInit(): void {
this.loadData().subscribe(somearray => { this.dataSource.data = somearray; });
}

This approach should also solve the problem (noted by one of the commenters here) of having the paginator rendered late when hidden behind an *ngIf template - the paginator will be sent to the data source even if it gets rendered very late.

Using async-await worked for me inside ngOnInit(), the paginator and sort has to wait!

   @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
.
.
.
ngOnInit() {
this.isLoading = true;
this._statsService.getAllCampgrounds().subscribe(
async (response) => {
this.allCampgrounds = response.allCampgrounds;
this.dataSource = await new MatTableDataSource(this.allCampgrounds);
        

this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;


this.isLoading = false;
},
(error) => {
this.isLoading = false;
console.log(error);
}
);
}

None of the anwsers posted above helped me in this case.

The reason why my pagination didn't work depsite correct implementation was import

e.x.

import {MatPaginator} from "@angular/material/paginator";

didn't work, so I changed this component import to

import { MatTableDataSource, MatPaginator, MatSort } from '@angular/material';

I had a similar issue. Added the following to resolve it.

Step1
Add ChangeDetectorRef to constructor E.g

constructor(private cdr: ChangeDetectorRef)

Step2
call detect change method and pagination in the init method

this.cdr.detectChanges();
this.dataSource.paginator = this.paginator;

It took me hours to figure it out.

The key is this.dataSource = new MatTableDataSource<MyInterface>(Object.values(data)); Then set this.dataSource.paginator = this.paginator;

I was using this.dataSource = data although I could get the data but the pagination was not working.

Again you have to use new MatTableDataSource

Works in Angular 11.

@ViewChild(MatPaginator, {static: false}) paginator: any // For pagination
@ViewChild(MatSort, {static: false}) sort: any; // For Sort

use like this it will resolve the issue.

I was having similar issue, As I had two mat tables with mat paginators and only one of them works. I tried all of the above options, then I realized I was updating the datasource object instead of datasource.data, didn't realized that I was updating the type, thanks to @Bigeyes for sharing his answer.

Table loading data but paginator not working:

this.datasource = data.filter(x => (x.planName == this.planName && x.Termed_y == 1))

Table loading data and paginator working:

this.dataSource2.data = data.filter(x => (x.planName == this.planName && x.Termed_y == 1))

As the most voted answer mentions, it is due to a conflict caused by *ngIf:

SOLUTIONS:

  1. Leave the mat-paginator out of the *ngIf conditional.

  2. Change the *ngIf conditional to something else that suits your needs.

    2.1 You can replace it with [hidden]="conditional".

    2.2 You can also use [ngClass]="'d-none': conditional", and create a class that contains display: none.

There are others but personally the one I use when it causes this type of inconvenience is 2.2