Компонент (AngularMatDataTable) не перерисовывается после изменений массива данных в подписке в ANGULAR? - PullRequest
1 голос
/ 30 сентября 2019

Я создаю отношения Parent / Child в Angular 8. Компонент Parrent вызывает сервисный вызов, который устанавливает наблюдаемое roleList, затем я передаю этот roleList дочернему компоненту. В дочернем компоненте я пытаюсь подписаться на переменную Input, которую получает дочерний элемент, и создаю экземпляр MatDataTableSource с новыми значениями.

Проблема заключается в следующем: Дочерний компонент получает значение, но компонент не-render. После того, как я вызываю изменение в приложении (например, при наведении мыши на другой компонент, который изменяет состояние), в нем отображаются данные, полученные от службы.

Это компоненты:

Родительский компонент .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>

Редактировать:

У меня есть ChangeDetectionStrategy.OnPush в app.component.ts . Когда я изменяю его на по умолчанию , он работает как положено. Но почему это не работает с onPush?

1 Ответ

0 голосов
/ 30 сентября 2019

Это потому, что вы создаете наблюдаемую информацию, объединяя this.sort.sortChange и this.paginator.page, а затем подписываясь на эту наблюдаемую и обновляя данные таблицы соответствия. Это означает, что всякий раз, когда происходит изменение в сортировке или разбивке на страницы, наблюдаемое будет выдавать значение, а таблица будет обновляться в подписке. Но любые значения, генерируемые в вашей наблюдаемой информации, не обрабатываются.

Решение:

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 .

Подробнее об этом можно прочитать здесь.

Редактировать:

При наличии OnPush на AppComponent все дочерний компонент changeDetectionStrategy выполняет все изменения дочернего компонентаТакже на Push. И, в этом случае, детектор изменений запускается только для дочернего компонента, когда изменяется любое входное значение, чего не происходит (наблюдаемые, генерирующие новые значения, не изменяют ссылку на объект). Чтобы вручную пометить компонент, который будет проверен с помощью changeDetector, введите «ChangeDetectorRef» и вызовите markForCheck, где вы подписываетесь на наблюдаемое.

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

ИЛИ лучший подход - это использовать AsyncPipe, как это делается дляyou.

Кроме того, вы не должны включать OnPush в AppComponent, а только указывать его в своих интеллектуальных компонентах, что позволит вам лучше понимать, как обнаруживать изменения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...