Angular + Material - як оновити джерело даних (мат-таблиця)


120

Я використовую таблицю мат, щоб перерахувати вміст вибраних користувачами мов. Вони також можуть додавати нові мови за допомогою діалогової панелі. Після того, як вони додали мову і повернулися назад. Я хочу, щоб мій джерело даних оновився, щоб показати зміни, які вони внесли.

Я ініціалізую сховище даних, отримуючи дані користувача від служби та передаючи їх у джерело даних методом оновлення.

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);   
    });
  }
}

language-data-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
Якщо ви хочете , щоб викликати зміну «з джерела даних», будь ласка , подивіться на stackoverflow.com/questions/47897694 / ...
Єнніфер

У цьому випадку можна використовувати емітери подій. stackoverflow.com/a/44858648/8300620
Rohit Parte

Відповіді:


58

Trigger виявлення зміни, використовуючи ChangeDetectorRefв refresh()способі тільки після отримання нових даних, ін'єкційного ChangeDetectorRef в конструкторах і використовувати 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();
    });
  }
}

9
Це, здається, працює, чи це правильний спосіб зробити це? Здається трохи хакітним ...
Кей

Які ще способи? Чи можете ви навести приклади у своєму рішенні для повної відповіді?
Кей


88

Я не знаю, чи ChangeDetectorRefпотрібне було, коли питання було створене, але зараз цього достатньо:

import { MatTableDataSource } from '@angular/material/table';

// ...

dataSource = new MatTableDataSource<MyDataType>();

refresh() {
  this.myService.doSomething().subscribe((data: MyDataType[]) => {
    this.dataSource.data = data;
  }
}

Приклад:
StackBlitz


4
Хоча це рішення насправді працює, воно заплутає пагінатор матеріалу, якщо елемент доданий де-небудь, крім першої сторінки результатів. Я розумію, що це не виходить за рамки цього питання, але оскільки вони пов'язані між собою, чи трапляється вам швидко вирішити цю проблему, яку ви могли б додати до своєї відповіді?
Лицар

5
@Knight Я думаю, що вам потрібно призначити Пагінатор до MatTableDataSource.paginatorвластивості після ініціалізації подання Пагінатара. Дивіться опис paginatorвластивості тут: material.angular.io/components/table/api#MatTableDataSource
Мартін Шнайдер

Хороша довідка. Я раніше цього не помічав у документах. Дякую!
Лицар

2
@ MA-Maddin Ви можете бути більш конкретними щодо того, як це зробити? приклад?
Натанфан

@Nathanphan пізно, але додав приклад;)
Мартін Шнайдер,

46

Тож для мене ніхто не дав належної відповіді на проблему, з якою я зіткнувся, і це майже те саме, що і @Kay. Для мене мова йде про сортування, в таблиці сортування не відбувається змін в килимку. Я маю на меті цю відповідь, оскільки це єдина тема, яку я знаходжу, шукаючи Google. Я використовую Angular 6.

Як сказано тут :

Оскільки таблиця оптимізована для продуктивності, вона не буде автоматично перевіряти зміни в масиві даних. Натомість, коли об’єкти додаються, видаляються або переміщуються на масив даних, ви можете запустити оновлення до виведених рядків таблиці, викликавши метод renderRows ().

Тому вам просто потрібно викликати renderRows () у вашому методі refresh (), щоб відобразити зміни.

Дивіться тут про інтеграцію.


1
Якщо джерело даних таблиці змінено на клієнті, ця відповідь, можливо, ви шукаєте. Чудово працює!
Альвін

Це правильна відповідь щодо кутового матеріалу 8
Том

Дякую. Але від якого об’єкта я повинен називати "renderRows ()"? Це в "this.datasource"?
WitnessTruth

19

Оскільки ви користуєтесь MatPaginator, вам просто потрібно зробити будь-яку зміну в програмі для редагування, це запускає перезавантаження даних.

Простий трюк:

this.paginator._changePageSize(this.paginator.pageSize); 

Це оновлює розмір сторінки до поточного розміру сторінки, тому в основному нічого не змінюється, за винятком того, що приватна _emitPageEvent()функція теж не називається, ініціює перезавантаження таблиці.


Я спробував ваш код, і він не працює (не має ефекту). Однак nextPage, а потім попередняPage знову працює, але рішення не є.
Ахмед Хасн.

_changePageSize()не є правдою? Чи безпечно використовувати? Детальніше про stackoverflow.com/questions/59093781/…
Джонс

9
this.dataSource = new MatTableDataSource<Element>(this.elements);

Додайте цей рядок під дією додавання чи видалення конкретного рядка.

refresh() {
  this.authService.getAuthenticatedUser().subscribe((res) => {
    this.user = new MatTableDataSource<Element>(res);   
  });
}

що це.елементи
парват

8

Найкращий спосіб зробити це - додавши додаткове спостереження до вашої реалізації даних.

У способі підключення ви вже повинні використовувати 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 () метод і відкликав його від екземпляра джерела даних без компонента та інших належних методів.


Цей спосіб може бути правильним. Для мене це добре працює
Ману

як я це реалізую, коли хочу розширити MatTableDataSource? Коли я пробую ваш приклад коду, я отримую помилкуProperty 'connect' in type 'customDataSource<T>' is not assignable to the same property in base type 'MatTableDataSource<T>'. Type '() => Observable<any>' is not assignable to type '() => BehaviorSubject<T[]>'. Type 'Observable<any>' is not assignable to type 'BehaviorSubject<T[]>'. Property '_value' is missing in type 'Observable<any>'.
Моріс,

1
@Maurice тип MatTableDataSource реалізує метод з'єднання з іншим типом повернення. Він використовує BehaviorSubject <t []>, що означає, що вам просто потрібно змінити приклад, щоб повернути це замість спостережуваного. Ви все ще можете мати можливість використовувати DataSource, але якщо вам доведеться використовувати MatTableDataSource, тоді поверніть BehaviorSubject, який підписаний на вашу спостережувану, припускаючи, що ви маєте з чого почати. Сподіваюся, що це допомагає. Ви можете посилатися на джерело для MatTableDataSource для точного синтаксису нового типу джерел даних: github.com/angular/material2/blob/master/src/lib/table/…
jogi

7

Ви можете просто скористатися функцією підключення джерела даних

this.datasource.connect().next(data);

як так. 'data' - це нові значення для даних


Мав потенціал, але, здається, не працює. Якщо після цього ви отримуєте доступ до this.datasource.data, його не оновлюється.
Rui Marques

4

Ви можете легко оновити дані таблиці за допомогою "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" кутовий, виявляйте зміни об'єкта (this.teachDS), і вам не потрібно використовувати іншу річ.

П.Д .: Це працює для мене в кутових 6 і 7, я не пробував іншої версії.


2
Так, це працює для мене, є проблемою щодо змінної посилання та значення, виявлення змін не бачить нових змін, для цього вам потрібно оновити їх.
Майра Родрігес

Це може працювати, якщо dataSource є лише масивом, але не тоді, коли dataSource є об'єктом MatTableDataSource.
Руй Маркес


3

Ну, я зіткнувся з подібною проблемою, коли я додав щось до джерела даних, і це не перезавантажується.

Найпростіший спосіб я виявив, що просто перепризначити дані

let dataSource = ['a','b','c']
dataSource.push('d')
let cloned = dataSource.slice()
// OR IN ES6 // let cloned = [...dataSource]
dataSource = cloned

Ідеально !! Це wokrs !! Дякую :)
Ніколас

3

У куті 9 секрет є 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);
}

2

Я досяг хорошого рішення, використовуючи два ресурси:

оновлення як джерела даних, так і пагінатора:

this.dataSource.data = this.users;
this.dataSource.connect().next(this.users);
this.paginator._changePageSize(this.paginator.pageSize);

де, наприклад , тут визначено джерело даних :

    users: User[];
    ...
    dataSource = new MatTableDataSource(this.users);
    ...
    this.dataSource.paginator = this.paginator;
    ...

1
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);   
    });
  }
}

8
Будь ласка, додайте пояснення до своєї відповіді, просто розміщення коду не дуже корисне і може призвести до видалення вашої відповіді.
TJ Wolschon

1

у моєму випадку (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 

1

Існує два способи зробити це, оскільки кутовий матеріал непослідовний, і це дуже погано зафіксовано. Таблиця кутових матеріалів не оновлюється, коли з'явиться новий рядок. Дивно, але це сказано, що це проблеми. Але це більше схоже на питання дизайну, вони не можуть змінитися. Слід очікувати, що таблиця буде оновлена, коли з’явиться новий рядок. Якщо ця поведінка не повинна бути включена за замовчуванням, повинен бути перемикач для її вимкнення.

У будь-якому випадку ми не можемо змінити кутовий матеріал. Але ми можемо використовувати дуже погано задокументований метод для цього:

Один - якщо ви використовуєте масив безпосередньо як джерело:

call table.renderRows()

де таблиця - ViewChild мат-таблиці

Друге - якщо ви використовуєте сортування та інші функції

table.renderRows () дивно не спрацює. Тому що мат-стіл тут непостійний. Вам потрібно скористатися хаком, щоб повідомити, що джерело змінилось. Ви робите це за допомогою цього методу:

this.dataSource.data = yourDataSource;

де dataSource - обгортка MatTableDataSource, яка використовується для сортування та інших функцій.


0

Це працювало для мене:

refreshTableSorce() {
    this.dataSource = new MatTableDataSource<Element>(this.newSource);
}

Це не ідеальне рішення, оскільки воно відтворює джерело для таблиці. І використовувати це з обтічним / сокетним не є ефективним.
Майхан Ніят

0

Я думаю, що MatTableDataSourceоб’єкт якимось чином пов'язаний з масивом даних, який ви передаєте MatTableDataSourceконструктору.

Наприклад:

dataTable: string[];
tableDS: MatTableDataSource<string>;

ngOnInit(){
   // here your pass dataTable to the dataSource
   this.tableDS = new MatTableDataSource(this.dataTable); 
}

Отже, коли вам доведеться змінити дані; змінити у вихідному списку, dataTableа потім відобразити зміни на таблиці методом виклику _updateChangeSubscription()на tableDS.

Наприклад:

this.dataTable.push('testing');
this.tableDS._updateChangeSubscription();

Це робота зі мною через кутовий 6.


4
Цей метод є префіксом підкресленням, _і ви його називаєте?
Стефан

0

Це працює для мене:

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


0

Я провів ще кілька досліджень і знайшов це місце, щоб дати мені те, що мені потрібно - відчуває себе чистою і стосується оновлення даних, коли оновлений з сервера: 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();
  }
}

organization.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>

organization.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);
  }
}

0

Ознайомившись із таблицею матеріалів, не оновленою оновленням даних про публікацію № 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
];

0

// це dataSource
this.guests = [];

this.guests.push ({id: 1, назва: 'Ricardo'});

// оновити dataSource this.guests = Array.from (this.guest);


0
npm install @matheo/datasource

Я випустив бібліотеку, яка в майбутньому має бути офіційним матеріальним джерелом даних, підтримуючи будь-які потоки введення (сортування, пагінація, фільтри) та деяку конфігурацію з налагодженням, щоб побачити, як вона працює під час кодування.

import { MatDataSourceModule } from '@matheo/datasource';

Ви можете знайти демонстрацію StackBlitz та додаткову інформацію тут:
https://medium.com/@matheo/reactive-datasource-for-angular-1d869b0155f6

Радий би почути вашу думку та підтримати ваші випадки використання, якщо це необхідно.
Щасливого кодування!


0

Я спробував ChangeDetectorRef, Subject і BehaviourSubject, але те, що працює для мене

dataSource = [];
this.dataSource = [];
 setTimeout(() =>{
     this.dataSource = this.tableData[data];
 },200)

що тут відбувається? Я відчуваю, що були допущені помилки з іменними змінними.
ChumiestBucket
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.