Angular + Material-データソースを更新する方法(マットテーブル)


120

マットテーブルを使用して、ユーザーが選択した言語のコンテンツを一覧表示しています。ダイアログパネルを使用して新しい言語を追加することもできます。彼らは言語を追加して戻った後。データソースを更新して、行った変更を表示します。

サービスからユーザーデータを取得し、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);   
    });
  }
}

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
  }

}

そのため、refreshメソッドを呼び出そうとして、ユーザーをバックエンドから再度取得し、データソースを再初期化しました。ただし、これは機能せず、変更は発生していません。


1
「データソースから」の変更をトリガーする場合は、stackoverflow.com
questions / 47897694 /…

この場合、イベントエミッターを使用できます。 stackoverflow.com/a/44858648/8300620
Rohit Parte

回答:


58

新しいデータを受け取った直後にメソッドChangeDetectorRefで変更検出をトリガーし、コンストラクターにChangeDetectorRefrefresh()挿入して、次のように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
このソリューションは実際には機能しますが、要素が結果の最初のページ以外の場所に追加されると、マテリアルのページネーターを台無しにします。それはこの質問の範囲を超えていることを理解していますが、2つは関連しているので、あなたはあなたの答えに追加できるその問題の迅速な解決策を偶然持っていますか?
ナイト

5
@Knight MatTableDataSource.paginatorPaginatorのビューが初期化された後で、Paginatorをプロパティに割り当てる必要があると思います。paginatorここのプロパティの説明を参照してください:material.angular.io/components/table/api#MatTableDataSource
Martin Schneider

良い参照。私は以前にドキュメントでそれを発見していませんでした。ありがとう!
ナイト

2
@ MA-Maddinは、具体的な方法を教えてください。例?
Nathanphan

@Nathanphanは遅くなりましたが、例を追加しました;)
Martin Schneider

46

だから私にとって、@ Kayとほとんど同じである、私が出会った問題に良い答えを出す人は誰もいませんでした。私にとってはソートについてですが、ソートテーブルはマットの変更が発生しません。これは私がグーグルを検索して見つけた唯一のトピックだからです。Angular 6を使用しています。

ここで言ったように

テーブルはパフォーマンスを最適化するため、データ配列への変更を自動的にチェックしません。代わりに、データ配列でオブジェクトが追加、削除、または移動されたときに、renderRows()メソッドを呼び出して、テーブルのレンダリングされた行の更新をトリガーできます。

あなただけ呼び出す必要がありますので、renderRowsを()自分の中でリフレッシュ()変更の現れを作る方法。

統合については、こちらをご覧ください。


1
クライアントでテーブルデータソースが変更された場合、この答えがおそらく探しているものです。よく働く!
Alvin Saldanha

これは、角度のある材料8での正解です
トム

ありがとうございました。しかし、どのオブジェクトから「renderRows()」を呼び出す必要がありますか?「this.datasource」にありますか?
WitnessTruth

19

あなたが使っているので MatPaginator、ページネーターを変更するだけで、データの再ロードがトリガーされます。

簡単なトリック:

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

これにより、ページサイズが現在のページサイズに更新されるため、private _emitPageEvent()関数も呼び出され、テーブルのリロードをトリガーする以外は、基本的に何も変更されません。


私はあなたのコードを試してみましたが、機能しません(効果がありません)。ただし、nextPage、次にpreviousPageは再び機能しますが、解決策ではありません。
Ahmed Hasn。

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

this.elementsとは
parvat

8

これを行う最良の方法は、データソース実装にオブザーバブルを追加することです。

connectメソッド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()メソッドで呼び出しをラップし、コンポーネントやその他の適切な手法を使用して、データソースインスタンスから呼び出します。


この方法は正しいかもしれません。それは私にとってはうまく
Manu

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>'.
Maurice

1
@MauriceタイプMatTableDataSourceは、異なる戻り値タイプの接続メソッドを実装します。BehaviorSubject <t []>を使用しています。これは、Observableではなく、例を変更してこれを返す必要があることを意味します。それでも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)の変更を検出し、別のものを使用する必要はありません。

PD:角度6と7でそれは私のために働きます、私は別のバージョンを試しませんでした。


2
はい、それは私にとってはうまくいきます、参照と値の変数に関する問題です、変更検出は新しい変更を認識しません、それはあなたがそれを更新する必要があるためです。
Mayra Rodriguez

これは、dataSourceが単なる配列の場合に機能しますが、dataSourceがMatTableDataSourceオブジェクトの場合には機能しません。
Rui Marques


3

まあ、私はデータソースに何かを追加し、それが再読み込みされないという同様の問題に遭遇しました。

私が見つけた最も簡単な方法は単にデータを再割り当てすることです

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

パーフェクト!! すごい!おかげで:)
ニコラス

3

Angular 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

2つのリソースを使用して適切なソリューションを実現しました。

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

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ウォルション

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

Angular Materialは一貫性がないため、これを行うには2つの方法があり、これは非常に不十分に文書化されています。新しい行が到着しても、角度マテリアルテーブルは更新されません。驚いたことに、それはパフォーマンスの問題が原因であると言われています。しかし、それは設計上の問題のように見え、変更できません。新しい行が発生したときにテーブルが更新されることが期待されます。この動作をデフォルトで有効にしない場合は、これをオフにするスイッチが必要です。

とにかく、角度マテリアルは変更できません。しかし、基本的には、十分に文書化されていない方法でそれを行うことができます。

1つ-配列をソースとして直接使用する場合:

call table.renderRows()

テーブルはマットテーブルのViewChildです。

2番目-並べ替えやその他の機能を使用する場合

table.renderRows()は驚くほど機能しません。マットテーブルはここで矛盾しているからです。ソースが変更されたことを伝えるには、ハックを使用する必要があります。あなたはこの方法でそれを行います:

this.dataSource.data = yourDataSource;

ここで、dataSourceは、並べ替えやその他の機能に使用されるMatTableDataSourceラッパーです。


0

これは私のために働きました:

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

テーブルのソースを再作成するため、理想的なソリューションではありません。合理化されたソケットでこれを使用することは効率的ではありません。
Maihan Nijat

0

私が思うにMatTableDataSource、オブジェクトがあなたに渡すことをデータ配列にリンクされ、いくつかの方法であるMatTableDataSourceコンストラクタ。

例えば:

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

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

したがって、データを変更する必要がある場合は、元のリストdataTableに変更を加えてから、call _updateChangeSubscription()メソッドonを使用してテーブルに変更を反映しtableDSます。

例えば:

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

これは、Angular 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

Material Tableを読んだ後、post data update#11638を更新していませんバグレポート 最良の(読んだ、最も簡単な解決策)は、EventEmitterの使用を提案した最終コメント投稿者の「shhdharmen」が提案したとおりです。

これには、生成されたデータソースクラスに対するいくつかの簡単な変更が含まれます。

つまり、データソースクラスに新しいプライベート変数を追加します。

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
];

0

//これはdataSourceです
this.guests = [];

this.guests.push({id:1、name: 'Ricardo'});

// dataSource this.guests = Array.from(this.guest);を更新します


0
npm install @matheo/datasource

将来、公式のMaterial 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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.