Angular + Material - 如何刷新数据源(mat-table)

时间:2017-10-14 16:10:10

标签: angular angular-material refresh

我使用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
  }

}

所以我试图调用一个刷新方法,然后我再次从后端获取用户,然后重新初始化数据源。但是这不起作用,不会发生任何变化。

27 个答案:

答案 0 :(得分:35)

使用refresh()方法中的ChangeDetectorRef触发更改检测  收到新数据后,在构造函数中注入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();
    });
  }
}

答案 1 :(得分:29)

我不知道问题创建时是否需要ChangeDetectorRef,但现在这已经足够了:

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

// ...

dataSource = new MatTableDataSource<MyDataType>();

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

答案 2 :(得分:18)

所以对我来说,没有人能很好地回答我遇到的与@Kay几乎相同的问题。对我来说,这是关于排序的,排序表不会在席位中发生更改。 我打算给出这个答案,因为这是我通过搜索google找到的唯一主题。 我正在使用Angular 6。

here所述:

  

由于表针对性能进行了优化,因此不会自动检查数据数组的更改。相反,当在数据数组上添加,删除或移动对象时,可以通过调用表的renderRows()方法来触发对表的渲染行的更新。

因此,您只需在 refresh()方法中调用 renderRows()即可显示更改。

有关集成,请参见here

答案 3 :(得分:12)

由于您使用的是MatPaginator,因此您只需要对paginator进行任何更改,这会触发数据重新加载。

简单的技巧:

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

这会将页面大小更新为当前页面大小,因此基本上没有任何更改,除了调用私有_emitPageEvent()函数,触发表重新加载。

答案 4 :(得分:10)

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

答案 5 :(得分:6)

最好的方法是在数据源实现中添加一个额外的observable。

在connect方法中,您应该已经使用Observable.merge订阅包含paginator.page,sort.sortChange等的可观察数组。您可以向此添加一个新主题并在其上调用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()方法中,并从组件中的数据源实例和其他适当的技术中调用它。

答案 6 :(得分:6)

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

在添加或删除特定行的操作下添加此行。

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

答案 7 :(得分:3)

您可以只使用数据源连接功能

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

像这样。 'data'是数据表的新值

答案 8 :(得分:2)

添加新数据行后,我通过更新 dataSource 而不使用其实例来刷新我的材料表。

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
    
  }

答案 9 :(得分:2)

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

答案 10 :(得分:2)

有两种方法可以执行此操作,因为“角度材质”不一致,并且文档记录很少。新行到达时,角度材质表不会更新。令人惊讶的是,这是因为性能问题。但它看起来更像是一个设计问题,它们无法更改。应该期望表在出现新行时更新。如果默认情况下不应启用此行为,则应有一个开关将其关闭。

无论如何,我们不能更改Angular Material。但是,我们基本上可以使用文献记录很少的方法来做到这一点:

一个-如果您直接使用数组作为源:

call table.renderRows()

其中table是mat-table的ViewChild

第二个-如果您使用排序和其他功能

table.renderRows()令人惊讶地无法正常工作。因为此处的mat-table不一致。您需要使用技巧来告诉源更改。您可以使用以下方法进行操作:

this.dataSource.data = yourDataSource;

其中dataSource是用于排序和其他功能的MatTableDataSource包装器。

答案 11 :(得分:2)

我尝试过ChangeDetectorRef,Subject和BehaviourSubject,但对我有用的

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

答案 12 :(得分: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 

答案 13 :(得分:1)

您可以使用“ 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,我没有尝试其他版本。

答案 14 :(得分:1)

我已经尝试了一些之前的建议。它确实更新了表格,但我有一些担忧:

  1. 使用其克隆更新 dataSource.data。例如
this.dataSource.data = [...this.dataSource.data];

如果数据很大,这将重新分配大量内存。此外,MatTable 认为表内的一切都是新的,因此可能会导致性能问题。我发现我的表格在我的表格有大约 300 行的地方闪烁。

  1. 呼叫paginator._changePageSize。例如
this.paginator._changePageSize(this.paginator.pageSize);

它将发出 page 事件。如果您已经对 page 事件进行了一些处理。您可能会觉得很奇怪,因为该事件可能会被多次触发。并且可能存在这样的风险,如果该事件以某种方式间接触发 _changePageSize(),则会导致无限循环......

我在这里建议另一种解决方案。如果您的表不依赖于 dataSourcefilter 字段。

  1. 您可以更新 filter 字段以触发表刷新:
this.dataSource.filter = ' '; // Note that it is a space, not empty string

通过这样做,表格将执行过滤,从而更新表格的 UI。但它需要您自己的 dataSource.filterPredicate() 来处理您的过滤逻辑。

答案 15 :(得分:1)

您还可以使用renderRows()方法。

@ViewChild(MatTable,{static:false})表:MatTable //初始化

然后   this.table.renderRows();

作为参考,请检查以下内容:: https://www.freakyjolly.com/angular-7-8-edit-add-delete-rows-in-material-table-with-using-dialogs-inline-row-operation/

答案 16 :(得分:0)

npm install @matheo/datasource

我发布了一个库,旨在将来成为正式的Material DataSource,它支持任何类型的输入流(排序,分页,过滤器)以及一些调试配置,以查看其在编码时的工作方式。

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

您可以在此处找到StackBlitz演示和更多信息:
https://medium.com/@matheo/reactive-datasource-for-angular-1d869b0155f6

很高兴听到您的意见,并在必要时支持您的用例。
编码愉快!

答案 17 :(得分:0)

试试这个可能对你有帮助

从加载用户的函数开始。

loadUser() {
    this.userService.getListOfUsers().subscribe()
        (response: any) => {
            this.dataSource = response
            this.dataSource.paginator = this.paginator;
        }

}

定义刷新函数,用于删除用户后刷新表。

refresh() {
    this.loadUser();
    this.dataSource.data = [...this.dataSource.data];
    this.dataSource.paginator = this.paginator;
}

现在您可以在完成删除用户进程后调用 refresh() 函数,如下所示。

deleteUser() {
    ...... 
    this.refresh()
}

答案 18 :(得分: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];
    }
  }

答案 19 :(得分:0)

好吧,我遇到了类似的问题,我向数据源添加了一些东西,但它没有重新加载 我发现最简单的方法只是重新评估数据

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

答案 20 :(得分: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();

通过Angular 6与我合作。

答案 21 :(得分:0)

我使用两种资源实现了很好的解决方案:

刷新dataSource和分页器:

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

答案 22 :(得分:0)

//这是数据源
this.guests = [];

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

//刷新数据源 this.guests = Array.from(this.guest);

答案 23 :(得分:0)

阅读Material Table not updating post data update #11638 Bug Report之后 我发现最好的(阅读,最简单的解决方案)是由最终评论者“ shhdharmen”建议的,并建议使用EventEmitter。

这涉及到对生成的数据源类的一些简单更改

ie)向您的数据源类添加新的私有变量

python -m pip install scipy<1.3 scikit-image<0.15

在将新数据推送到内部数组(this.data)的位置,我发出一个事件。

import { EventEmitter } from '@angular/core';
...
private tableDataUpdated = new EventEmitter<any>();

最后,在'connect'方法中更改'dataMutation'数组-如下

public addRow(row:myRowInterface) {
    this.data.push(row);
    this.tableDataUpdated.emit();
}

答案 24 :(得分:0)

这对我有用:

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

答案 25 :(得分:0)

我做了一些进一步的研究,发现这个地方可以满足我的需求-从服务器刷新后感觉很干净,并且与更新数据有关: https://blog.angular-university.io/angular-material-data-table/

上页的大部分信用。下面是一个示例,说明如何在选择更改时如何使用垫选择器更新绑定到数据源的垫表。我正在使用Angular7。很抱歉,我的建议过于广泛,试图做到完整而简洁—我已尽可能地删除了许多不需要的部分。希望以此帮助其他人更快地前进!

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

答案 26 :(得分: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