MatSort and MatPaginator does not work without set

2020-02-14 07:50发布

I have a data coming from an endpoint and put it into MatTableDataSource. I was able to get it working for MatSort and MatPaginator, but needed to use setTimeOut, which does not seems to be a proper way to do this. If I remove this, it will complain that 'Can not read property of sort undefined' which I assumed this is due to it's not initialized yet.

I also have tried:

  • to move moving it to afterviewinit, but the data was loaded after afterviewinit getting called so it still does not work
  • using this.changeDetectorRef.detectChanges() after this.datasource = new ... also does not work

This is my current code (which is working, but using settimeout)

<div *ngIf="!isLoading">
    <div *ngFor="let record of renderedData | async" matSort>

    // ... some html to show the 'record'

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

The Component

export class ResultsComponent implements OnInit, OnDestroy, AfterViewInit {
    dataSource: MatTableDataSource<any> = new MatTableDataSource();
    renderedData: Observable<any>;

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

    constructor(some service) {}

    ngOnInit() {
        const accountId = someOtherService.getAccountId();
        this.someService.getData(accountId)
            .subscribe((myData) => {
                    this.dataSource = new MatTableDataSource(myData);

                    // it won't work properly if it is not wrapped in timeout
                    setTimeout(() => {
                        this.dataSource.paginator = this.paginator;
                        this.sort.sort(<MatSortable>({id: 'created_on', start: 'desc'}));
                        this.dataSource.sort = this.sort;
                    });

                    this.renderedData = this.dataSource.connect();
                }
            });
    }

    ngAfterViewInit() {
    }

    ngOnDestroy(): void {
        if (this.dataSource) { this.dataSource.disconnect(); }
    }
}

The above code is working for me, I'm just looking the right way not to use settimeout if possible.

1条回答
Melony?
2楼-- · 2020-02-14 08:44

There are a couple of lifecycle timing issues at play here, and when you think about it, this is right.

The MatSort is part of the view, so it isn't 'ready' during OnInit - it is undefined. So trying to use it throws the error.

The MatSort is ready in AfterViewInit, but things are further complicated by the fact that you need to 'apply' the sort to the datasource after performing the sort, and this triggers changes to the view by way of the renderedData that is 'connected' to the datasource. You therefore end up with an ExpressionChangedAfterItHasBeenCheckedError because the view initialization lifecycle hasn't completed but you are already trying to change it.

So you can't sort until the view is ready, and you can't apply the sort when you are notified that the view is ready. The only thing you can do is wait until the end of the component initialization lifecycle. And you can do that using setTimeout().

I don't think there is any way around both of these problems without setTimeout(), so in that case it doesn't matter whether you call it from OnInit or AfterViewInit.

A couple of other observations on your code:

You don't need to create a new instance of MatTableDataSource in your subscription. You can assign the result data to the already created datasource:

this.dataSource.data = myData;

This frees you up from having to connect the rendered data to the datasource afterward, so you can do it when you initialize the datasource:

dataSource: MatTableDataSource<any> = new MatTableDataSource();
renderedData: Observable<any> = this.dataSource.connect();
查看更多
登录 后发表回答