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


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

语言数据源

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/…–
Yennefer

在这种情况下,可以使用事件发射器。 stackoverflow.com/a/44858648/8300620
Rohit Parte

Answers:


58

触发一个变化检测通过使用ChangeDetectorRefrefresh()方法只接收新的数据,注射后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
这似乎可行,这是正确的方法吗?似乎有点hacky ...
Kay

还有哪些其他方式?您能否在解决方案中提供示例以获取完整答案?


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的视图之后将Paginator 分配给该属性。请参阅paginator此处的属性说明:material.angular.io/components/table/api#MatTableDataSource
Martin Schneider

很好的参考。我以前没有在文档中发现它。谢谢!
骑士

2
@ MA-Maddin您可以更具体地了解它吗?例?
Nathanphan

@Nathanphan晚了,但添加了一个示例;)
Martin Schneider19年

46

因此,对我而言,没有人能很好地回答我遇到的与@Kay几乎相同的问题。对我来说,这是关于排序的,排序表不会发生在垫子上的变化。我有这个目的,因为这是我通过搜索google找到的唯一主题。我正在使用Angular 6。

这里所说:

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

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

请参阅此处进行集成。


1
如果在客户端上更改了表数据源,那么您可能正在寻找答案。很棒!
艾文·萨尔达尼亚

从角材料8开始,这是正确的答案
汤姆(Tom)

谢谢。但是我应该从哪个对象调用“ renderRows()”?它在“ this.datasource”中吗?
WitnessTruth

19

由于您正在使用MatPaginator,您只需要对分页器进行任何更改,就会触发数据重新加载。

简单技巧:

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

这会将页面大小更新为当前页面大小,因此基本上没有什么变化,除了私有_emitPageEvent()函数也被调用之外,触发了表的重新加载。


我尝试了您的代码,但它不起作用(无效)。但是nextPage和previousPage仍然可以工作,但是没有解决方案。
艾哈迈德·哈森。

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

这是什么。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类型使用不同的返回类型实现connect方法。它使用BehaviorSubject <t []>,这意味着您只需要更改示例以返回此值即可,而不是Observable。您仍然应该可以使用DataSource,但如果必须使用MatTableDataSource,则返回一个已订阅可观察对象的BehaviorSubject,并假设您要从中开始。希望能有所帮助。您可以参考MatTableDataSource的源以获取新数据源类型的确切语法:github.com/angular/material2/blob/master/src/lib/table/…–
jogi

7

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

this.datasource.connect().next(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
是的,它对我有用,是有关引用和值var的问题,更改检测未看到新更改,因为您需要对其进行更新。
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

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

刷新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 Wolschon '18

1

就我而言(Angular 6+),我继承自MatTableDataSourcecreate 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()

其中table是mat-table的ViewChild

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

table.renderRows()令人惊讶地无法正常工作。因为mat-table在这里不一致。您需要使用hack来告知源已更改。您可以使用以下方法进行操作:

this.dataSource.data = yourDataSource;

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



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与我合作。


4
该方法以下划线为前缀,_您称之为?
Stephane '18

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/

大部分积分都在上面的页面上。下面是一个示例,该示例说明了如何在选择更改时使用垫选择器更新绑定到数据源的垫表。我正在使用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();
  }
}

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>

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

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

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

const dataMutations = [
    this.tableDataUpdated,
    this.paginator.page,
    this.sort.sortChange
];


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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.