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

Я был бы рад услышать ваше мнение и поддержать ваши варианты использования, если это необходимо.
Удачного кодирования!