Angular + Material - 如何刷新数据源(mat-table)
Angular + Material - How to refresh a data source (mat-table)
我正在使用 mat-table 来列出用户选择的语言的内容。他们还可以使用对话框面板添加新语言。在他们添加了一种语言并返回后。我希望我的数据源刷新以显示他们所做的更改。
我通过从服务获取用户数据并将其传递到刷新方法中的数据源来初始化数据存储。
Language.component.ts
import { Component, OnInit } from '@angular/core';
import { LanguageModel, LANGUAGE_DATA } from '../../../../models/language.model';
import { LanguageAddComponent } from './language-add/language-add.component';
import { AuthService } from '../../../../services/auth.service';
import { LanguageDataSource } from './language-data-source';
import { LevelbarComponent } from '../../../../directives/levelbar/levelbar.component';
import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { MatSnackBar, MatDialog } from '@angular/material';
@Component({
selector: 'app-language',
templateUrl: './language.component.html',
styleUrls: ['./language.component.scss']
})
export class LanguageComponent implements OnInit {
displayedColumns = ['name', 'native', 'code', 'level'];
teachDS: any;
user: any;
constructor(private authService: AuthService, private dialog: MatDialog) { }
ngOnInit() {
this.refresh();
}
add() {
this.dialog.open(LanguageAddComponent, {
data: { user: this.user },
}).afterClosed().subscribe(result => {
this.refresh();
});
}
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = res;
this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);
});
}
}
语言数据-source.ts
import {MatPaginator, MatSort} from '@angular/material';
import {DataSource} from '@angular/cdk/collections';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
export class LanguageDataSource extends DataSource<any> {
constructor(private languages) {
super();
}
connect(): Observable<any> {
return Observable.of(this.languages);
}
disconnect() {
// No-op
}
}
所以我尝试调用刷新方法,再次从后端获取用户,然后重新初始化数据源。但是这不起作用,没有发生任何变化。
在 refresh()
方法中使用 ChangeDetectorRef
触发变化检测
在收到新数据后,像这样注入 ChangeDetectorRef in the constructor and use detectChanges:
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { LanguageModel, LANGUAGE_DATA } from '../../../../models/language.model';
import { LanguageAddComponent } from './language-add/language-add.component';
import { AuthService } from '../../../../services/auth.service';
import { LanguageDataSource } from './language-data-source';
import { LevelbarComponent } from '../../../../directives/levelbar/levelbar.component';
import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { MatSnackBar, MatDialog } from '@angular/material';
@Component({
selector: 'app-language',
templateUrl: './language.component.html',
styleUrls: ['./language.component.scss']
})
export class LanguageComponent implements OnInit {
displayedColumns = ['name', 'native', 'code', 'level'];
teachDS: any;
user: any;
constructor(private authService: AuthService, private dialog: MatDialog,
private changeDetectorRefs: ChangeDetectorRef) { }
ngOnInit() {
this.refresh();
}
add() {
this.dialog.open(LanguageAddComponent, {
data: { user: this.user },
}).afterClosed().subscribe(result => {
this.refresh();
});
}
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = res;
this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);
this.changeDetectorRefs.detectChanges();
});
}
}
由于您正在使用 MatPaginator
,您只需对分页器进行任何更改,这会触发数据重新加载。
简单的技巧:
this.paginator._changePageSize(this.paginator.pageSize);
这会将页面大小更新为当前页面大小,所以基本上没有任何变化,除了私有 _emitPageEvent()
函数也被调用,触发 table 重新加载。
执行此操作的最佳方法是向您的数据源实现添加一个额外的可观察对象。
在 connect 方法中,您应该已经使用 Observable.merge
来订阅包含 paginator.page、sort.sortChange 等的可观察对象数组。您可以将新主题添加到this 并在需要刷新时调用 next。
像这样:
export class LanguageDataSource extends DataSource<any> {
recordChange$ = new Subject();
constructor(private languages) {
super();
}
connect(): Observable<any> {
const changes = [
this.recordChange$
];
return Observable.merge(...changes)
.switchMap(() => return Observable.of(this.languages));
}
disconnect() {
// No-op
}
}
然后你可以调用recordChange$.next()
发起刷新。
自然地,我会将调用包装在 refresh() 方法中,并从数据源实例 w/in 组件和其他适当的技术中调用它。
this.dataSource = new MatTableDataSource<Element>(this.elements);
在添加或删除特定行的操作下方添加此行。
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = new MatTableDataSource<Element>(res);
});
}
我不知道创建问题时是否需要ChangeDetectorRef
,但现在这就足够了:
import { MatTableDataSource } from '@angular/material/table';
// ...
dataSource = new MatTableDataSource<MyDataType>();
refresh() {
this.myService.doSomething().subscribe((data: MyDataType[]) => {
this.dataSource.data = data;
}
}
示例:
StackBlitz
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
export class LanguageComponent implemnts OnInit {
displayedColumns = ['name', 'native', 'code', 'leavel'];
user: any;
private update = new Subject<void>();
update$ = this.update.asObservable();
constructor(private authService: AuthService, private dialog: MatDialog) {}
ngOnInit() {
this.update$.subscribe(() => { this.refresh()});
}
setUpdate() {
this.update.next();
}
add() {
this.dialog.open(LanguageAddComponent, {
data: { user: this.user },
}).afterClosed().subscribe(result => {
this.setUpdate();
});
}
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = res;
this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);
});
}
}
所以对我来说,没有人对我遇到的问题给出好的答案,这几乎与@Kay 相同。对我来说这是关于排序,排序 table 不会发生垫子的变化。
我的目的是回答这个问题,因为它是我通过搜索 google 找到的唯一主题。
我正在使用 Angular 6.
如前所述here:
Since the table optimizes for performance, it will not automatically check for changes to the data array. Instead, when objects are added, removed, or moved on the data array, you can trigger an update to the table's rendered rows by calling its renderRows() method.
因此,您只需在 refresh() 方法中调用 renderRows() 即可显示您的更改。
请参阅 进行整合。
这对我有用:
refreshTableSorce() {
this.dataSource = new MatTableDataSource<Element>(this.newSource);
}
我认为 MatTableDataSource
对象以某种方式与您传递给 MatTableDataSource
构造函数的数据数组相关联。
例如:
dataTable: string[];
tableDS: MatTableDataSource<string>;
ngOnInit(){
// here your pass dataTable to the dataSource
this.tableDS = new MatTableDataSource(this.dataTable);
}
因此,当您必须更改数据时;在原始列表 dataTable
上进行更改,然后通过在 tableDS
.
上调用 _updateChangeSubscription()
方法在 table 上反映更改
例如:
this.dataTable.push('testing');
this.tableDS._updateChangeSubscription();
我一直在努力 Angular 6.
这对我有用:
dataSource = new MatTableDataSource<Dict>([]);
public search() {
let url = `${Constants.API.COMMON}/dicts?page=${this.page.number}&` +
(this.name == '' ? '' : `name_like=${this.name}`);
this._http.get<Dict>(url).subscribe((data)=> {
// this.dataSource = data['_embedded'].dicts;
this.dataSource.data = data['_embedded'].dicts;
this.page = data['page'];
this.resetSelection();
});
}
所以你应该将你的数据源实例声明为MatTableDataSource
就我而言 (Angular 6+),我从 MatTableDataSource
继承来创建 MyDataSource
。 在 this.data = someArray
之后不调用
this.entitiesSubject.next(this.data as T[])
未显示的数据
class 我的数据源
export class MyDataSource<T extends WhateverYouWant> extends MatTableDataSource<T> {
private entitiesSubject = new BehaviorSubject<T[]>([]);
loadDataSourceData(someArray: T[]){
this.data = someArray //whenever it comes from an API asyncronously or not
this.entitiesSubject.next(this.data as T[])// Otherwise data not displayed
}
public connect(): BehaviorSubject<T[]> {
return this.entitiesSubject
}
}//end Class
我做了更多研究,发现这个地方可以满足我的需求 - 感觉很干净并且与从服务器刷新时的更新数据相关:
https://blog.angular-university.io/angular-material-data-table/
以上页面的大部分功劳。下面是一个示例,说明如何使用 mat-selector 在选择更改时更新绑定到数据源的 mat-table。我正在使用 Angular 7. 抱歉,内容过于广泛,试图做到完整但简洁 - 我已经尽可能多地删除了不需要的部分。希望能帮助其他人更快地前进!
organization.model.ts:
export class Organization {
id: number;
name: String;
}
organization.service.ts:
import { Observable, empty } from 'rxjs';
import { of } from 'rxjs';
import { Organization } from './organization.model';
export class OrganizationService {
getConstantOrganizations(filter: String): Observable<Organization[]> {
if (filter === "All") {
let Organizations: Organization[] = [
{ id: 1234, name: 'Some data' }
];
return of(Organizations);
} else {
let Organizations: Organization[] = [
{ id: 5678, name: 'Some other data' }
];
return of(Organizations);
}
// ...just a sample, other filterings would go here - and of course data instead fetched from server.
}
organizationdatasource.model.ts:
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { catchError, finalize } from "rxjs/operators";
import { OrganizationService } from './organization.service';
import { Organization } from './organization.model';
export class OrganizationDataSource extends DataSource<Organization> {
private organizationsSubject = new BehaviorSubject<Organization[]>([]);
private loadingSubject = new BehaviorSubject<boolean>(false);
public loading$ = this.loadingSubject.asObservable();
constructor(private organizationService: OrganizationService, ) {
super();
}
loadOrganizations(filter: String) {
this.loadingSubject.next(true);
return this.organizationService.getOrganizations(filter).pipe(
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false))
).subscribe(organization => this.organizationsSubject.next(organization));
}
connect(collectionViewer: CollectionViewer): Observable<Organization[]> {
return this.organizationsSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.organizationsSubject.complete();
this.loadingSubject.complete();
}
}
organizations.component.html:
<div class="spinner-container" *ngIf="organizationDataSource.loading$ | async">
<mat-spinner></mat-spinner>
</div>
<div>
<form [formGroup]="formGroup">
<mat-form-field fxAuto>
<div fxLayout="row">
<mat-select formControlName="organizationSelectionControl" (selectionChange)="updateOrganizationSelection()">
<mat-option *ngFor="let organizationSelectionAlternative of organizationSelectionAlternatives"
[value]="organizationSelectionAlternative">
{{organizationSelectionAlternative.name}}
</mat-option>
</mat-select>
</div>
</mat-form-field>
</form>
</div>
<mat-table fxLayout="column" [dataSource]="organizationDataSource">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let organization">{{organization.name}}</mat-cell>
</ng-container>
<ng-container matColumnDef="number">
<mat-header-cell *matHeaderCellDef>Number</mat-header-cell>
<mat-cell *matCellDef="let organization">{{organization.number}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
organizations.component.scss:
.spinner-container {
height: 360px;
width: 390px;
position: fixed;
}
organization.component.ts:
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs';
import { OrganizationService } from './organization.service';
import { Organization } from './organization.model';
import { OrganizationDataSource } from './organizationdatasource.model';
@Component({
selector: 'organizations',
templateUrl: './organizations.component.html',
styleUrls: ['./organizations.component.scss']
})
export class OrganizationsComponent implements OnInit {
public displayedColumns: string[];
public organizationDataSource: OrganizationDataSource;
public formGroup: FormGroup;
public organizationSelectionAlternatives = [{
id: 1,
name: 'All'
}, {
id: 2,
name: 'With organization update requests'
}, {
id: 3,
name: 'With contact update requests'
}, {
id: 4,
name: 'With order requests'
}]
constructor(
private formBuilder: FormBuilder,
private organizationService: OrganizationService) { }
ngOnInit() {
this.formGroup = this.formBuilder.group({
'organizationSelectionControl': []
})
const toSelect = this.organizationSelectionAlternatives.find(c => c.id == 1);
this.formGroup.get('organizationSelectionControl').setValue(toSelect);
this.organizationDataSource = new OrganizationDataSource(this.organizationService);
this.displayedColumns = ['name', 'number' ];
this.updateOrganizationSelection();
}
updateOrganizationSelection() {
this.organizationDataSource.loadOrganizations(this.formGroup.get('organizationSelectionControl').value.name);
}
}
您可以使用 "concat" 轻松更新 table 的数据:
例如:
language.component.ts
teachDS: any[] = [];
language.component.html
<table mat-table [dataSource]="teachDS" class="list">
并且,当您更新数据时 (language.component.ts):
addItem() {
// newItem is the object added to the list using a form or other way
this.teachDS = this.teachDS.concat([newItem]);
}
当你使用 "concat" angular 检测对象的变化时 (this.teachDS) 而你不需要使用其他东西。
PD:它在 angular 6 和 7 中对我有用,我没有尝试其他版本。
您可以只使用数据源连接功能
this.datasource.connect().next(data);
像这样。 'data' 是数据表的新值
看完Material Table not updating post data update #11638 Bug Report
我发现最好的(阅读,最简单的解决方案)是最终评论者 'shhdharmen' 建议使用 EventEmitter 的建议。
这涉及对生成的数据源进行一些简单的更改class
ie) 向您的数据源添加一个新的私有变量 class
import { EventEmitter } from '@angular/core';
...
private tableDataUpdated = new EventEmitter<any>();
在我将新数据推送到内部数组 (this.data) 的地方,我发出了一个事件。
public addRow(row:myRowInterface) {
this.data.push(row);
this.tableDataUpdated.emit();
}
最后,更改 'connect' 方法中的 'dataMutation' 数组 - 如下
const dataMutations = [
this.tableDataUpdated,
this.paginator.page,
this.sort.sortChange
];
// this is the dataSource
this.guests = [];
this.guests.push({id: 1, name: 'Ricardo'});
// refresh the dataSource
this.guests = Array.from(this.guest);
我使用两个资源实现了一个很好的解决方案:
正在刷新数据源和分页器:
this.dataSource.data = this.users;
this.dataSource.connect().next(this.users);
this.paginator._changePageSize(this.paginator.pageSize);
这里定义了例如 dataSource:
users: User[];
...
dataSource = new MatTableDataSource(this.users);
...
this.dataSource.paginator = this.paginator;
...
好吧,我 运行 遇到了类似的问题,我向数据源添加了一些东西,但没有重新加载。
我发现最简单的方法就是分配数据
let dataSource = ['a','b','c']
dataSource.push('d')
let cloned = dataSource.slice()
// OR IN ES6 // let cloned = [...dataSource]
dataSource = cloned
您也可以使用 renderRows() 方法。
@ViewChild(MatTable, {static: false}) table : MatTable // 初始化
然后
this.table.renderRows();
我尝试过 ChangeDetectorRef、Subject 和 BehaviourSubject,但对我有用的
dataSource = [];
this.dataSource = [];
setTimeout(() =>{
this.dataSource = this.tableData[data];
},200)
有两种方法可以做到这一点,因为 Angular Material 不一致,而且这方面的文档很少。 Angular material table 不会在新行到达时更新。令人惊讶的是它被告知这是因为性能问题。但它看起来更像是一个设计问题,他们无法改变。应该期望 table 在出现新行时更新。如果默认情况下不应启用此行为,则应该有一个开关将其关闭。
反正也改不了Angular Material。但是我们基本上可以使用一个文档很少的方法来做:
一个——如果直接使用数组作为源:
call table.renderRows()
其中 table 是垫子的 ViewChild-table
其次 - 如果您使用排序和其他功能
table.renderRows() 出人意料地不起作用。因为这里mat-table不一致。您需要使用 hack 来告知源已更改。你用这个方法来做:
this.dataSource.data = yourDataSource;
其中 dataSource 是用于排序和其他功能的 MatTableDataSource 包装器。
在Angular9中,秘密是this.dataSource.data = this.dataSource.data;
示例:
import { MatTableDataSource } from '@angular/material/table';
dataSource: MatTableDataSource<MyObject>;
refresh(): void {
this.applySomeModif();
// Do what you want with dataSource
this.dataSource.data = this.dataSource.data;
}
applySomeModif(): void {
// add some data
this.dataSource.data.push(new MyObject());
// delete index number 4
this.dataSource.data.splice(4, 0);
}
在 Angular 10 中,这对我有用:
在 HTML:
<mat-table [dataSource]="myArray">
在组件 TS 中:
myArray: MyObject[] = [];
addObjectToTable(object:MyObject): void {
//TO PREVENT DUPLICATED OBJECTS
if (object&& !this.myArray.includes(object)) {
this.myArray.push(object);
// TO FORCE DATA-TABLE's DATASOURCE TO REFRESH
this.myArray= [...this.myArray];
}
}
我已经尝试了之前的一些建议。它确实更新了 table 但我有一些担忧:
- 正在用它的克隆更新
dataSource.data
。例如
this.dataSource.data = [...this.dataSource.data];
如果数据很大,这将重新分配大量内存。此外,MatTable 认为 table 中的所有内容都是新的,因此可能会导致性能问题。我发现我的 table 在我的 table 有大约 300 行的地方闪烁。
- 正在呼叫
paginator._changePageSize
。例如
this.paginator._changePageSize(this.paginator.pageSize);
它将发出 page
事件。如果您已经对 page
事件进行了一些处理。您可能会觉得很奇怪,因为该事件可能会被触发不止一次。如果事件以某种方式间接触发_changePageSize()
,则可能存在风险,这将导致无限循环...
我在这里建议另一种解决方案。如果您的 table 不依赖于 dataSource
的 filter
字段。
- 您可以更新
filter
字段以触发 table 刷新:
this.dataSource.filter = ' '; // Note that it is a space, not empty string
这样做,table 将执行过滤,从而更新 table 的 UI。但它需要您自己 dataSource.filterPredicate()
来处理您的过滤逻辑。
试试这个也许对你有帮助
从加载用户的功能开始。
loadUser() {
this.userService.getListOfUsers().subscribe()
(response: any) => {
this.dataSource = response
this.dataSource.paginator = this.paginator;
}
}
定义刷新函数,用于删除用户后刷新table。
refresh() {
this.loadUser();
this.dataSource.data = [...this.dataSource.data];
this.dataSource.paginator = this.paginator;
}
现在您可以在完成删除用户进程后调用refresh()函数,如下所示。
deleteUser() {
......
this.refresh()
}
添加新数据行后,我通过更新数据源而不使用其实例来刷新 material table。
table 在 HTML:
<table mat-table #table [dataSource]="myDataArray">
addUser() 在 component.ts:
public USER_DATA: user[] = [];
public newUser = {userName: "ABC", email: "abc@gmail.com"};
public myDataArray: any;
addUser() {
const newUsersArray = this.USER_DATA;
newUsersArray.push(this.newUser);
this.myDataArray = [...newUsersArray];//refresh the dataSource
}
我正在使用 mat-table 来列出用户选择的语言的内容。他们还可以使用对话框面板添加新语言。在他们添加了一种语言并返回后。我希望我的数据源刷新以显示他们所做的更改。
我通过从服务获取用户数据并将其传递到刷新方法中的数据源来初始化数据存储。
Language.component.ts
import { Component, OnInit } from '@angular/core';
import { LanguageModel, LANGUAGE_DATA } from '../../../../models/language.model';
import { LanguageAddComponent } from './language-add/language-add.component';
import { AuthService } from '../../../../services/auth.service';
import { LanguageDataSource } from './language-data-source';
import { LevelbarComponent } from '../../../../directives/levelbar/levelbar.component';
import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { MatSnackBar, MatDialog } from '@angular/material';
@Component({
selector: 'app-language',
templateUrl: './language.component.html',
styleUrls: ['./language.component.scss']
})
export class LanguageComponent implements OnInit {
displayedColumns = ['name', 'native', 'code', 'level'];
teachDS: any;
user: any;
constructor(private authService: AuthService, private dialog: MatDialog) { }
ngOnInit() {
this.refresh();
}
add() {
this.dialog.open(LanguageAddComponent, {
data: { user: this.user },
}).afterClosed().subscribe(result => {
this.refresh();
});
}
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = res;
this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);
});
}
}
语言数据-source.ts
import {MatPaginator, MatSort} from '@angular/material';
import {DataSource} from '@angular/cdk/collections';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
export class LanguageDataSource extends DataSource<any> {
constructor(private languages) {
super();
}
connect(): Observable<any> {
return Observable.of(this.languages);
}
disconnect() {
// No-op
}
}
所以我尝试调用刷新方法,再次从后端获取用户,然后重新初始化数据源。但是这不起作用,没有发生任何变化。
在 refresh()
方法中使用 ChangeDetectorRef
触发变化检测
在收到新数据后,像这样注入 ChangeDetectorRef in the constructor and use detectChanges:
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { LanguageModel, LANGUAGE_DATA } from '../../../../models/language.model';
import { LanguageAddComponent } from './language-add/language-add.component';
import { AuthService } from '../../../../services/auth.service';
import { LanguageDataSource } from './language-data-source';
import { LevelbarComponent } from '../../../../directives/levelbar/levelbar.component';
import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { MatSnackBar, MatDialog } from '@angular/material';
@Component({
selector: 'app-language',
templateUrl: './language.component.html',
styleUrls: ['./language.component.scss']
})
export class LanguageComponent implements OnInit {
displayedColumns = ['name', 'native', 'code', 'level'];
teachDS: any;
user: any;
constructor(private authService: AuthService, private dialog: MatDialog,
private changeDetectorRefs: ChangeDetectorRef) { }
ngOnInit() {
this.refresh();
}
add() {
this.dialog.open(LanguageAddComponent, {
data: { user: this.user },
}).afterClosed().subscribe(result => {
this.refresh();
});
}
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = res;
this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);
this.changeDetectorRefs.detectChanges();
});
}
}
由于您正在使用 MatPaginator
,您只需对分页器进行任何更改,这会触发数据重新加载。
简单的技巧:
this.paginator._changePageSize(this.paginator.pageSize);
这会将页面大小更新为当前页面大小,所以基本上没有任何变化,除了私有 _emitPageEvent()
函数也被调用,触发 table 重新加载。
执行此操作的最佳方法是向您的数据源实现添加一个额外的可观察对象。
在 connect 方法中,您应该已经使用 Observable.merge
来订阅包含 paginator.page、sort.sortChange 等的可观察对象数组。您可以将新主题添加到this 并在需要刷新时调用 next。
像这样:
export class LanguageDataSource extends DataSource<any> {
recordChange$ = new Subject();
constructor(private languages) {
super();
}
connect(): Observable<any> {
const changes = [
this.recordChange$
];
return Observable.merge(...changes)
.switchMap(() => return Observable.of(this.languages));
}
disconnect() {
// No-op
}
}
然后你可以调用recordChange$.next()
发起刷新。
自然地,我会将调用包装在 refresh() 方法中,并从数据源实例 w/in 组件和其他适当的技术中调用它。
this.dataSource = new MatTableDataSource<Element>(this.elements);
在添加或删除特定行的操作下方添加此行。
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = new MatTableDataSource<Element>(res);
});
}
我不知道创建问题时是否需要ChangeDetectorRef
,但现在这就足够了:
import { MatTableDataSource } from '@angular/material/table';
// ...
dataSource = new MatTableDataSource<MyDataType>();
refresh() {
this.myService.doSomething().subscribe((data: MyDataType[]) => {
this.dataSource.data = data;
}
}
示例:
StackBlitz
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
export class LanguageComponent implemnts OnInit {
displayedColumns = ['name', 'native', 'code', 'leavel'];
user: any;
private update = new Subject<void>();
update$ = this.update.asObservable();
constructor(private authService: AuthService, private dialog: MatDialog) {}
ngOnInit() {
this.update$.subscribe(() => { this.refresh()});
}
setUpdate() {
this.update.next();
}
add() {
this.dialog.open(LanguageAddComponent, {
data: { user: this.user },
}).afterClosed().subscribe(result => {
this.setUpdate();
});
}
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = res;
this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);
});
}
}
所以对我来说,没有人对我遇到的问题给出好的答案,这几乎与@Kay 相同。对我来说这是关于排序,排序 table 不会发生垫子的变化。 我的目的是回答这个问题,因为它是我通过搜索 google 找到的唯一主题。 我正在使用 Angular 6.
如前所述here:
Since the table optimizes for performance, it will not automatically check for changes to the data array. Instead, when objects are added, removed, or moved on the data array, you can trigger an update to the table's rendered rows by calling its renderRows() method.
因此,您只需在 refresh() 方法中调用 renderRows() 即可显示您的更改。
请参阅
这对我有用:
refreshTableSorce() {
this.dataSource = new MatTableDataSource<Element>(this.newSource);
}
我认为 MatTableDataSource
对象以某种方式与您传递给 MatTableDataSource
构造函数的数据数组相关联。
例如:
dataTable: string[];
tableDS: MatTableDataSource<string>;
ngOnInit(){
// here your pass dataTable to the dataSource
this.tableDS = new MatTableDataSource(this.dataTable);
}
因此,当您必须更改数据时;在原始列表 dataTable
上进行更改,然后通过在 tableDS
.
_updateChangeSubscription()
方法在 table 上反映更改
例如:
this.dataTable.push('testing');
this.tableDS._updateChangeSubscription();
我一直在努力 Angular 6.
这对我有用:
dataSource = new MatTableDataSource<Dict>([]);
public search() {
let url = `${Constants.API.COMMON}/dicts?page=${this.page.number}&` +
(this.name == '' ? '' : `name_like=${this.name}`);
this._http.get<Dict>(url).subscribe((data)=> {
// this.dataSource = data['_embedded'].dicts;
this.dataSource.data = data['_embedded'].dicts;
this.page = data['page'];
this.resetSelection();
});
}
所以你应该将你的数据源实例声明为MatTableDataSource
就我而言 (Angular 6+),我从 MatTableDataSource
继承来创建 MyDataSource
。 在 this.data = someArray
this.entitiesSubject.next(this.data as T[])
未显示的数据
class 我的数据源
export class MyDataSource<T extends WhateverYouWant> extends MatTableDataSource<T> {
private entitiesSubject = new BehaviorSubject<T[]>([]);
loadDataSourceData(someArray: T[]){
this.data = someArray //whenever it comes from an API asyncronously or not
this.entitiesSubject.next(this.data as T[])// Otherwise data not displayed
}
public connect(): BehaviorSubject<T[]> {
return this.entitiesSubject
}
}//end Class
我做了更多研究,发现这个地方可以满足我的需求 - 感觉很干净并且与从服务器刷新时的更新数据相关: https://blog.angular-university.io/angular-material-data-table/
以上页面的大部分功劳。下面是一个示例,说明如何使用 mat-selector 在选择更改时更新绑定到数据源的 mat-table。我正在使用 Angular 7. 抱歉,内容过于广泛,试图做到完整但简洁 - 我已经尽可能多地删除了不需要的部分。希望能帮助其他人更快地前进!
organization.model.ts:
export class Organization {
id: number;
name: String;
}
organization.service.ts:
import { Observable, empty } from 'rxjs';
import { of } from 'rxjs';
import { Organization } from './organization.model';
export class OrganizationService {
getConstantOrganizations(filter: String): Observable<Organization[]> {
if (filter === "All") {
let Organizations: Organization[] = [
{ id: 1234, name: 'Some data' }
];
return of(Organizations);
} else {
let Organizations: Organization[] = [
{ id: 5678, name: 'Some other data' }
];
return of(Organizations);
}
// ...just a sample, other filterings would go here - and of course data instead fetched from server.
}
organizationdatasource.model.ts:
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { catchError, finalize } from "rxjs/operators";
import { OrganizationService } from './organization.service';
import { Organization } from './organization.model';
export class OrganizationDataSource extends DataSource<Organization> {
private organizationsSubject = new BehaviorSubject<Organization[]>([]);
private loadingSubject = new BehaviorSubject<boolean>(false);
public loading$ = this.loadingSubject.asObservable();
constructor(private organizationService: OrganizationService, ) {
super();
}
loadOrganizations(filter: String) {
this.loadingSubject.next(true);
return this.organizationService.getOrganizations(filter).pipe(
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false))
).subscribe(organization => this.organizationsSubject.next(organization));
}
connect(collectionViewer: CollectionViewer): Observable<Organization[]> {
return this.organizationsSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.organizationsSubject.complete();
this.loadingSubject.complete();
}
}
organizations.component.html:
<div class="spinner-container" *ngIf="organizationDataSource.loading$ | async">
<mat-spinner></mat-spinner>
</div>
<div>
<form [formGroup]="formGroup">
<mat-form-field fxAuto>
<div fxLayout="row">
<mat-select formControlName="organizationSelectionControl" (selectionChange)="updateOrganizationSelection()">
<mat-option *ngFor="let organizationSelectionAlternative of organizationSelectionAlternatives"
[value]="organizationSelectionAlternative">
{{organizationSelectionAlternative.name}}
</mat-option>
</mat-select>
</div>
</mat-form-field>
</form>
</div>
<mat-table fxLayout="column" [dataSource]="organizationDataSource">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
<mat-cell *matCellDef="let organization">{{organization.name}}</mat-cell>
</ng-container>
<ng-container matColumnDef="number">
<mat-header-cell *matHeaderCellDef>Number</mat-header-cell>
<mat-cell *matCellDef="let organization">{{organization.number}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
organizations.component.scss:
.spinner-container {
height: 360px;
width: 390px;
position: fixed;
}
organization.component.ts:
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs';
import { OrganizationService } from './organization.service';
import { Organization } from './organization.model';
import { OrganizationDataSource } from './organizationdatasource.model';
@Component({
selector: 'organizations',
templateUrl: './organizations.component.html',
styleUrls: ['./organizations.component.scss']
})
export class OrganizationsComponent implements OnInit {
public displayedColumns: string[];
public organizationDataSource: OrganizationDataSource;
public formGroup: FormGroup;
public organizationSelectionAlternatives = [{
id: 1,
name: 'All'
}, {
id: 2,
name: 'With organization update requests'
}, {
id: 3,
name: 'With contact update requests'
}, {
id: 4,
name: 'With order requests'
}]
constructor(
private formBuilder: FormBuilder,
private organizationService: OrganizationService) { }
ngOnInit() {
this.formGroup = this.formBuilder.group({
'organizationSelectionControl': []
})
const toSelect = this.organizationSelectionAlternatives.find(c => c.id == 1);
this.formGroup.get('organizationSelectionControl').setValue(toSelect);
this.organizationDataSource = new OrganizationDataSource(this.organizationService);
this.displayedColumns = ['name', 'number' ];
this.updateOrganizationSelection();
}
updateOrganizationSelection() {
this.organizationDataSource.loadOrganizations(this.formGroup.get('organizationSelectionControl').value.name);
}
}
您可以使用 "concat" 轻松更新 table 的数据:
例如:
language.component.ts
teachDS: any[] = [];
language.component.html
<table mat-table [dataSource]="teachDS" class="list">
并且,当您更新数据时 (language.component.ts):
addItem() {
// newItem is the object added to the list using a form or other way
this.teachDS = this.teachDS.concat([newItem]);
}
当你使用 "concat" angular 检测对象的变化时 (this.teachDS) 而你不需要使用其他东西。
PD:它在 angular 6 和 7 中对我有用,我没有尝试其他版本。
您可以只使用数据源连接功能
this.datasource.connect().next(data);
像这样。 'data' 是数据表的新值
看完Material Table not updating post data update #11638 Bug Report 我发现最好的(阅读,最简单的解决方案)是最终评论者 'shhdharmen' 建议使用 EventEmitter 的建议。
这涉及对生成的数据源进行一些简单的更改class
ie) 向您的数据源添加一个新的私有变量 class
import { EventEmitter } from '@angular/core';
...
private tableDataUpdated = new EventEmitter<any>();
在我将新数据推送到内部数组 (this.data) 的地方,我发出了一个事件。
public addRow(row:myRowInterface) {
this.data.push(row);
this.tableDataUpdated.emit();
}
最后,更改 'connect' 方法中的 'dataMutation' 数组 - 如下
const dataMutations = [
this.tableDataUpdated,
this.paginator.page,
this.sort.sortChange
];
// this is the dataSource
this.guests = [];
this.guests.push({id: 1, name: 'Ricardo'});
// refresh the dataSource
this.guests = Array.from(this.guest);
我使用两个资源实现了一个很好的解决方案:
正在刷新数据源和分页器:
this.dataSource.data = this.users;
this.dataSource.connect().next(this.users);
this.paginator._changePageSize(this.paginator.pageSize);
这里定义了例如 dataSource:
users: User[];
...
dataSource = new MatTableDataSource(this.users);
...
this.dataSource.paginator = this.paginator;
...
好吧,我 运行 遇到了类似的问题,我向数据源添加了一些东西,但没有重新加载。
我发现最简单的方法就是分配数据
let dataSource = ['a','b','c']
dataSource.push('d')
let cloned = dataSource.slice()
// OR IN ES6 // let cloned = [...dataSource]
dataSource = cloned
您也可以使用 renderRows() 方法。
@ViewChild(MatTable, {static: false}) table : MatTable // 初始化
然后 this.table.renderRows();
我尝试过 ChangeDetectorRef、Subject 和 BehaviourSubject,但对我有用的
dataSource = [];
this.dataSource = [];
setTimeout(() =>{
this.dataSource = this.tableData[data];
},200)
有两种方法可以做到这一点,因为 Angular Material 不一致,而且这方面的文档很少。 Angular material table 不会在新行到达时更新。令人惊讶的是它被告知这是因为性能问题。但它看起来更像是一个设计问题,他们无法改变。应该期望 table 在出现新行时更新。如果默认情况下不应启用此行为,则应该有一个开关将其关闭。
反正也改不了Angular Material。但是我们基本上可以使用一个文档很少的方法来做:
一个——如果直接使用数组作为源:
call table.renderRows()
其中 table 是垫子的 ViewChild-table
其次 - 如果您使用排序和其他功能
table.renderRows() 出人意料地不起作用。因为这里mat-table不一致。您需要使用 hack 来告知源已更改。你用这个方法来做:
this.dataSource.data = yourDataSource;
其中 dataSource 是用于排序和其他功能的 MatTableDataSource 包装器。
在Angular9中,秘密是this.dataSource.data = this.dataSource.data;
示例:
import { MatTableDataSource } from '@angular/material/table';
dataSource: MatTableDataSource<MyObject>;
refresh(): void {
this.applySomeModif();
// Do what you want with dataSource
this.dataSource.data = this.dataSource.data;
}
applySomeModif(): void {
// add some data
this.dataSource.data.push(new MyObject());
// delete index number 4
this.dataSource.data.splice(4, 0);
}
在 Angular 10 中,这对我有用: 在 HTML:
<mat-table [dataSource]="myArray">
在组件 TS 中:
myArray: MyObject[] = [];
addObjectToTable(object:MyObject): void {
//TO PREVENT DUPLICATED OBJECTS
if (object&& !this.myArray.includes(object)) {
this.myArray.push(object);
// TO FORCE DATA-TABLE's DATASOURCE TO REFRESH
this.myArray= [...this.myArray];
}
}
我已经尝试了之前的一些建议。它确实更新了 table 但我有一些担忧:
- 正在用它的克隆更新
dataSource.data
。例如
this.dataSource.data = [...this.dataSource.data];
如果数据很大,这将重新分配大量内存。此外,MatTable 认为 table 中的所有内容都是新的,因此可能会导致性能问题。我发现我的 table 在我的 table 有大约 300 行的地方闪烁。
- 正在呼叫
paginator._changePageSize
。例如
this.paginator._changePageSize(this.paginator.pageSize);
它将发出 page
事件。如果您已经对 page
事件进行了一些处理。您可能会觉得很奇怪,因为该事件可能会被触发不止一次。如果事件以某种方式间接触发_changePageSize()
,则可能存在风险,这将导致无限循环...
我在这里建议另一种解决方案。如果您的 table 不依赖于 dataSource
的 filter
字段。
- 您可以更新
filter
字段以触发 table 刷新:
this.dataSource.filter = ' '; // Note that it is a space, not empty string
这样做,table 将执行过滤,从而更新 table 的 UI。但它需要您自己 dataSource.filterPredicate()
来处理您的过滤逻辑。
试试这个也许对你有帮助
从加载用户的功能开始。
loadUser() {
this.userService.getListOfUsers().subscribe()
(response: any) => {
this.dataSource = response
this.dataSource.paginator = this.paginator;
}
}
定义刷新函数,用于删除用户后刷新table。
refresh() {
this.loadUser();
this.dataSource.data = [...this.dataSource.data];
this.dataSource.paginator = this.paginator;
}
现在您可以在完成删除用户进程后调用refresh()函数,如下所示。
deleteUser() {
......
this.refresh()
}
添加新数据行后,我通过更新数据源而不使用其实例来刷新 material table。
table 在 HTML:
<table mat-table #table [dataSource]="myDataArray">
addUser() 在 component.ts:
public USER_DATA: user[] = [];
public newUser = {userName: "ABC", email: "abc@gmail.com"};
public myDataArray: any;
addUser() {
const newUsersArray = this.USER_DATA;
newUsersArray.push(this.newUser);
this.myDataArray = [...newUsersArray];//refresh the dataSource
}