没有 setTimeOut,MatSort 和 MatPaginator 不起作用

Posted

技术标签:

【中文标题】没有 setTimeOut,MatSort 和 MatPaginator 不起作用【英文标题】:MatSort and MatPaginator does not work without setTimeOut 【发布时间】:2019-03-31 12:02:58 【问题描述】:

我有一个来自端点的数据并将其放入 MatTableDataSource。我能够让它为 MatSort 和 MatPaginator 工作,但需要使用 setTimeOut,这似乎不是正确的方法。如果我删除它,它会抱怨“无法读取未定义排序的属性”,我认为这是因为它尚未初始化。

我也试过了:

将其移至 afterviewinit,但数据是在之后加载的 afterviewinit 被调用所以它仍然不起作用 在 this.datasource = 之后使用 this.changeDetectorRef.detectChanges() 新...也不起作用

这是我当前的代码(正在运行,但使用 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>

组件

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(); 
    

上面的代码对我有用,我只是在寻找不使用 settimeout 的正确方法。

【问题讨论】:

通过订阅创建 MatTableDataSource 时我也必须这样做。期待看到这个问题的答案。 更好的解决方法可能是在this.dataSource = ... 之后调用ChangeDetectorRef.detecChanges(),而不是使用setTimeout @ConnorsFan 试过了,它给了我“Cannot read property 'sort' of undefined at SafeSubscriber._next”。我在构造函数中添加private changeDetectorRef:ChangeDetectorRef,然后在this.datasource = new Mat下面... this.changeDetectorRef.detectChanges(); @ConnorsFan, detectChanges 对当前视图和所有孩子进行全面检查,这是一种非常昂贵的方式,与这种方式相比,空的 setTimeout 看起来并不邪恶,恕我直言 【参考方案1】:

这里有几个生命周期时间问题,仔细想想,这是对的。

MatSort 是视图的一部分,因此在 OnInit 期间它还没有“准备好” - 它是未定义的。所以尝试使用它会引发错误。

MatSort 已在 AfterViewInit 中准备就绪,但由于您需要在执行排序后将排序“应用”到数据源这一事实,事情变得更加复杂,这会通过 renderData 触发对视图的更改,即“连接到数据源。因此,您最终会遇到 ExpressionChangedAfterItHasBeenCheckedError,因为视图初始化生命周期尚未完成,但您已经在尝试更改它。

因此,在视图准备就绪之前,您无法进行排序,并且在通知您视图准备就绪时,您也无法应用排序。您唯一能做的就是等到组件初始化生命周期结束。你可以使用 setTimeout() 来做到这一点。

我认为如果没有 setTimeout() 就无法解决这两个问题,所以在这种情况下,无论是从 OnInit 还是 AfterViewInit 调用它都无关紧要。

对您的代码的其他一些观察:

您无需在订阅中创建新的 MatTableDataSource 实例。您可以将结果数据分配给已经创建的数据源:

this.dataSource.data = myData;

这使您不必事后将呈现的数据连接到数据源,因此您可以在初始化数据源时执行此操作:

dataSource: MatTableDataSource<any> = new MatTableDataSource();
renderedData: Observable<any> = this.dataSource.connect();

【讨论】:

很好的解释 很好的解释。谢谢你。我可能应该提出一个新问题,但无论如何在排序发生后检索第一个元素?相同的代码 *ngFor DIV 之一中的元素?获取外部 DIV(带有*ngIf="!isLoading" 的那个)并抓住第一个孩子。【参考方案2】:

实际上有一种方法可以在不使用 setTimeout 的情况下做到这一点。这适用于 Angular 9,我没有在以前的版本上测试过,所以我不确定它是否在那里工作。

我在这里找到了这个解决方案:https://github.com/angular/components/issues/10205

而不是放:

@ViewChild(MatSort) sort: MatSort;

对 matSort 使用 setter。一旦模板中的 matSort 更改(即第一次定义),此设置器将触发,当您通过单击箭头更改排序时,它不会触发。这将如下所示:

    @ViewChild(MatSort) set matSort(sort: MatSort) 
        this.dataSource.sort = sort;
    

您可以对分页器使用相同的解决方案。

【讨论】:

以上是关于没有 setTimeOut,MatSort 和 MatPaginator 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

使用 MatSort 的 Angular 材质

MatSort 打破 MatTable 详细信息行动画

Angular MatSort.sortChange:尝试从函数更新排序时忽略事件数据

setTimeout与setInterval对比

没有setTimeout 0,不理解堆栈

我将如何使用setTimeout每X次运行某些内容?