ANGULAR 中订阅中的数据数组更改后,组件 (AngularMatDataTable) 不会重新呈现?

Component (AngularMatDataTable) doesn't re-render after data array changes in subscription in ANGULAR?

我正在 Angular 中创建一个 Parent/Child 关系 8. 父组件调用设置可观察对象 roleList 的服务调用,然后我将此 roleList 传递给子组件。在子组件中,我尝试订阅子组件接收的 Input 变量并使用新值实例化 MatDataTableSource。

问题是:子组件收到值但组件不重新呈现。在我调用应用程序中的更改后(例如:将鼠标悬停到另一个更改状态的组件),它确实显示了从服务接收到的数据。

这些是组件:

父组件.ts:

import { Component, OnInit, ChangeDetectionStrategy } from 
'@angular/core';
import { RoleService } from '../role.service';
import { RoleTemplateList } from '../../../../core/models/role-template- 
list.model';
import { Observable } from 'rxjs';

@Component({
selector: 'kt-view-roles',
templateUrl: './view-roles.component.html',
styleUrls: ['./view-roles.component.scss'],
})
export class ViewRolesComponent implements OnInit {
roleList: Observable<RoleTemplateList[]>;
columns = ['id', 'name', 'skills', 'actions'];
actions = ['actions'];

constructor(private roleService: RoleService) {}

ngOnInit() {
    this.roleList = this.roleService.getRoles();
}
}

父组件。html:

<kt-portlet-header [class]="'kt-portlet__head--lg'">
    <ng-container ktPortletTitle>
        <h3 class="kt-portlet__head-title">
            <span>All Roles</span>
        </h3>
    </ng-container>
</kt-portlet-header>

<kt-portlet-body>
    <kt-data-table-new [columnsToDisplay]="columns" [data]="roleList"></kt-data-table-new>

</kt-portlet-body>

</kt-portlet>

这是我尝试订阅并使用订阅结果初始化 MatTableDataSource 的组件。

子组件.ts:

import { Component, OnInit, Input, AfterViewInit, ViewChild, OnChanges, 
ChangeDetectionStrategy } from '@angular/core';
import { Observable, merge, of } from 'rxjs';
import { MatPaginator, MatSort, MatTableDataSource } from 
'@angular/material';
import { startWith, switchMap, map, catchError, filter, tap } from 
'rxjs/operators';

@Component({
selector: 'kt-data-table-new',
templateUrl: './data-table-new.component.html',
styleUrls: ['./data-table-new.component.scss']
})
export class DataTableNewComponent implements AfterViewInit {

@Input() columnsToDisplay: any[];
@Input() data: Observable<any[]>;
dataSource: MatTableDataSource<any>;
isLoadingResults = true;
dataLength = 0;

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

constructor() { }

ngAfterViewInit() {
    this.sort.sortChange.subscribe(() => this.paginator.pageIndex= 0);
    merge(this.sort.sortChange, this.paginator.page)
        .pipe(
            startWith({}),
            switchMap(() => {
                this.isLoadingResults = true;
                return this.data;
            }),
            map(data => {
                this.isLoadingResults = false;
                this.dataLength = data.length;
                return data;
            }),
            catchError(() => {
                this.isLoadingResults = false;
                return of([]);
            }),
        ).subscribe(value => this.dataSource = new MatTableDataSource(value));
}

}

子组件 .html:

<div class="example-container mat-elevation-z8">
<div class="example-loading-shade" *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="example-table-container">
    <table mat-table [dataSource]="dataSource" class="example-table" matSort matSortActive="created"
        matSortDisableClear matSortDirection="desc">
        <ng-container [matColumnDef]="column" *ngFor="let column of columnsToDisplay">
            <th mat-header-cell *matHeaderCellDef>
                {{column}}
            </th>
            <td mat-cell *matCellDef="let element"> {{element[column]}} </td>
        </ng-container>

        <tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
        <tr mat-row *matRowDef="let row; columns: columnsToDisplay;"></tr>
    </table>
</div>
<mat-paginator [length]="dataLength" [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>

编辑:

我在app.component.ts中有ChangeDetectionStrategy.OnPush。当我将其修改为 default 时,它按预期工作。但为什么它不适用于 onPush?

这是因为您正在通过合并 this.sort.sortChange 和 this.paginator.page 创建一个可观察对象,然后订阅该可观察对象并更新 mat table 数据。这意味着,每当排序或分页发生变化时,observable 将发出一个值,并且 table 将在订阅中更新。但是,您的数据可观察值中发出的任何值都不会被处理。

解法:

1) 在创建可观察对象时合并 this.data,这将允许订阅读取数据变化发出的值。

merge(this.data, this.sort.sortChange, this.paginator.page)

2)(首选)发送数据时使用异步管道

<kt-data-table-new [columnsToDisplay]="columns" [data]="roleList | async"></kt-data-table-new>

子组件:

@Input() data: any[];

异步管道在组件销毁时自动取消订阅可观察对象以避免潜在的内存泄漏。异步管道还标记要检查的组件,如果您使用 ChangeDetectionStrategy.OnPush,这将非常有用。

您可以阅读更多相关信息

编辑:

在 AppComponent 上使用 OnPush 也会使所有子组件的 changeDetectionStrategy onPush。而且,在这种情况下,只有当任何输入值发生变化时,变化检测器才会为子组件运行,而它不会(发出新值的 Observables 不会改变对象引用)。要手动标记要由 changeDetector 检查的组件,请注入"ChangeDetectorRef",然后在订阅可观察对象的位置调用 markForCheck。

constructor(private cd: ChangeDetectorRef) { }
//in subscribe block after updating table
this.cd.markForCheck();

或者更好的方法是使用 AsyncPipe,因为它会为您做到这一点。

此外,你不应该在 AppComponent 上有 OnPush,只把它放在你的智能组件中,这会让你更清楚地处理变化检测。