Angular + Material - Как обновить источник данных (mat-table)
Я использую мат-таблицу для отображения содержимого выбранных пользователями языков. Они также могут добавлять новые языки с помощью диалоговой панели. После они добавили язык и вернулись обратно. Я хочу, чтобы мой источник данных обновился, чтобы показать изменения, которые они внесли.
Я инициализирую хранилище данных, получая пользовательские данные из службы и передавая их в источник данных в методе refresh.
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
}
}
Итак, я попытался вызвать метод обновления, где я снова получаю пользователя из бэкэнда, а затем повторно инициализирую источник данных. Однако это не работает, никаких изменений не происходит.
Ответы
Ответ 1
Инициируйте обнаружение изменений, используя ChangeDetectorRef
в методе refresh()
сразу после получения новых данных, вставьте ChangeDetectorRef в конструктор и используйте DeteChanges следующим образом:
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();
});
}
}
Ответ 2
Я не знаю, требовался ли ChangeDetectorRef
при создании вопроса, но сейчас этого достаточно:
import { MatTableDataSource } from '@angular/material/table';
// ...
dataSource = new MatTableDataSource<MyDataType>();
refresh() {
this.myService.doSomething().subscribe((data: MyDataType[]) => {
this.dataSource.data = data;
}
}
Пример:
StackBlitz
Ответ 3
Так что для меня никто не дал хорошего ответа на проблему, с которой я столкнулся, которая почти совпадает с @Kay. Для меня это про сортировку, в таблице сортировки не происходит изменений в мате. Я нацеливаю этот ответ, так как это единственная тема, которую я нахожу при поиске в Google. Я использую Angular 6.
Как сказано здесь:
Поскольку таблица оптимизируется по производительности, она не будет автоматически проверять изменения в массиве данных. Вместо этого, когда объекты добавляются, удаляются или перемещаются в массив данных, вы можете инициировать обновление отображаемых строк таблицы, вызывая его метод renderRows().
Так что вам просто нужно вызвать renderRows() в вашем методе refresh(), чтобы ваши изменения появились.
Смотрите здесь для интеграции.
Ответ 4
Поскольку вы используете MatPaginator
, вам просто нужно сделать какое-либо изменение в paginator, это приведет к перезагрузке данных.
Простой трюк:
this.paginator._changePageSize(this.paginator.pageSize);
Это обновляет размер страницы до текущего размера страницы, поэтому в основном ничего не меняется, за исключением того, что _emitPageEvent()
частная _emitPageEvent()
, инициирующая перезагрузку таблицы.
Ответ 5
this.dataSource = new MatTableDataSource<Element>(this.elements);
Добавьте эту строку ниже вашего действия по добавлению или удалению определенной строки.
refresh() {
this.authService.getAuthenticatedUser().subscribe((res) => {
this.user = new MatTableDataSource<Element>(res);
});
}
Ответ 6
Лучший способ сделать это - добавить дополнительное наблюдение к реализации Datasource.
В методе подключения вы уже должны использовать Observable.merge
для подписки на массив наблюдаемых, которые включают в себя страницу paginator.page, sort.sortChange и т.д. Вы можете добавить к этому тему новую тему и позвонить дальше, когда вы необходимо вызвать обновление.
что-то вроде этого:
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() и вызвал его из экземпляра datasource w/в компоненте и других надлежащих методов.
Ответ 7
Вы можете просто использовать функцию подключения источника данных
this.datasource.connect().next(data);
вот так. "data" - это новые значения для данных
Ответ 8
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);
});
}
}
Ответ 9
в моем случае (Angular 6+) я унаследовал от MatTableDataSource
для создания MyDataSource
. Без вызова после this.data = someArray
this.entitiesSubject.next(this.data as T[])
данные, где не отображаются
класс MyDataSource
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
Ответ 10
Вы можете легко обновить данные таблицы, используя "concat":
например:
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: Это работает для меня в угловых 6 и 7, я не пробовал другую версию.
Ответ 11
Ну, я столкнулся с подобной проблемой, когда я добавил что-то в источник данных, а он не перезагружался.
Я нашел самый простой способ переназначения данных
let dataSource = ['a','b','c']
dataSource.push('d')
let cloned = dataSource.slice()
// OR IN ES6 // let cloned = [...dataSource]
dataSource = cloned
Ответ 12
Это сработало для меня:
refreshTableSorce() {
this.dataSource = new MatTableDataSource<Element>(this.newSource);
}
Ответ 13
Я думаю, что объект MatTableDataSource
каким-то образом связан с массивом данных, который вы передаете конструктору MatTableDataSource
.
Например:
dataTable: string[];
tableDS: MatTableDataSource<string>;
ngOnInit(){
// here your pass dataTable to the dataSource
this.tableDS = new MatTableDataSource(this.dataTable);
}
Итак, когда вам нужно изменить данные; измените исходный список dataTable
и затем dataTable
изменение в таблице методом _updateChangeSubscription()
на tableDS
.
Например:
this.dataTable.push('testing');
this.tableDS._updateChangeSubscription();
Это работает со мной через Angular 6.
Ответ 14
Это работает на меня:
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
Ответ 15
Я провел еще какое-то исследование и нашел это место, чтобы дать мне то, что мне нужно - он чувствует себя чисто и относится к обновлению данных при обновлении с сервера: https://blog.angular-university.io/angular-material-data-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);
}
}
Ответ 16
После прочтения Таблицы материалов, не обновляющей пост-данные, обновление # 11638 Отчет об ошибках Я нашел лучшее (читай, самое простое решение), как было предложено финальным комментатором 'shhdharmen' с предложением использовать EventEmitter.
Это включает в себя несколько простых изменений в сгенерированном классе источника данных
т.е.) добавить новую приватную переменную в ваш класс источника данных
import { EventEmitter } from '@angular/core';
...
private tableDataUpdated = new EventEmitter<any>();
и где я помещаю новые данные во внутренний массив (this.data), я генерирую событие.
public addRow(row:myRowInterface) {
this.data.push(row);
this.tableDataUpdated.emit();
}
и, наконец, измените массив dataMutation в методе connect:
const dataMutations = [
this.tableDataUpdated,
this.paginator.page,
this.sort.sortChange
];
Ответ 17
//это источник данных
this.ghest = [];
this.ghest.push({id: 1, name: 'Ricardo'});
//обновляем источник данных this.ghest = Array.from(this.guest);
Ответ 18
Я достиг хорошего решения, используя два ресурса:
Обновление dataSource и paginator:
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;
...
Ответ 19
npm install @matheo/datasource
Я выпустил библиотеку, которая в будущем должна стать официальным источником данных материалов, поддерживающую любые виды входных потоков (сортировка, разбиение на страницы, фильтры) и некоторую конфигурацию с отладкой, чтобы увидеть, как она работает во время кодирования.
import { MatDataSourceModule } from '@matheo/datasource';
Вы можете найти демо StackBlitz и дополнительную информацию здесь:
https://medium.com/@matheo/reactive-datasource-for-angular-1d869b0155f6
Я был бы рад услышать ваше мнение и поддержать ваши варианты использования, если это необходимо.
Удачного кодирования!
Ответ 20
Вы также можете использовать метод renderRows().
@ViewChild (MatTable, {static: false}) table: MatTable//initialize
тогда this.table.renderRows();
для справки проверьте это:https://www.freakyjolly.com/angular-7-8-edit-add-delete-rows-in-material-table-with-using-dialogs-inline-row-operation/