简体   繁体   English

反应角材料数据表

[英]Reactive Angular Material Data Table

I've created an Angular Material Data Table with this ng generate @angular/material:material-table command and it gave me following file structure: 我用这个ng generate @angular/material:material-table命令创建了一个Angular Material Data Table,它给了我以下文件结构:

  • table-datasource.ts 表datasource.ts
  • table.component.ts table.component.ts
  • table.component.html table.component.html

The idea here is to do all the fetching, sorting and pagination in the table-datasource.ts . 这里的想法是在table-datasource.ts进行所有获取,排序和分页。 By default the data is placed in an Array inside table-datasource.ts but in my case its coming from an ngxs-store which exposes an Observable of an Array . 默认情况下,数据放在table-datasource.ts中的一个数组table-datasource.ts但在我的情况下,它来自一个公开一个Observable of Array的ngxs-store。 Atm I have following implementation: 我有以下实施:

table-datasource.ts: 表datasource.ts:

export class TokenTableDataSource extends DataSource<TokenTableItem> {
  @Select(TokenTableState.getTokenTableItems) private tokenTableItems$:Observable<TokenTableItem[]>;
  totalItems$ = new BehaviorSubject<TokenTableItem[]>([]);

  constructor(private paginator: MatPaginator, private sort: MatSort) {
    super();
  }

  /**
  * Connect this data source to the table. The table will only update when
  * the returned stream emits new items.
  * @returns A stream of the items to be rendered.
  */
  connect(): Observable<TokenTableItem[]> {
    this.tokenTableItems$.subscribe(item => this.totalItems$.next(item));

    // init on first connect
    if (this.totalItems$.value === undefined) {
      this.totalItems$.next([]);
      this.paginator.length = this.totalItems$.value.length;
    }
    // Combine everything that affects the rendered data into one update
    // stream for the data-table to consume.
    const dataMutations = [
      observableOf(this.totalItems$),
      this.paginator.page,
      this.sort.sortChange
    ];

    return merge(...dataMutations).pipe(
      map(() =>  this.totalItems$.next(this.getPagedData(this.getSortedData([...this.totalItems$.value])))),
      mergeMap(() => this.totalItems$)
    );
  }
  ...generated paging and sorting methods

table-component.html: 表component.html:

<div class="mat-elevation-z8">
  <table mat-table class="full-width-table" [dataSource]="dataSource" matSort aria-label="Elements">

  ...multiple columns

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>

  <mat-paginator #paginator
      [length]="this.dataSource.totalItems$.value?.length"
      [pageIndex]="pageIndex"
      [pageSize]="pageSize"
      [pageSizeOptions]="pageSizeOptions"
      [showFirstLastButtons]=true
      (page)="handlePage($event)">
  </mat-paginator>
</div>

table.component.ts: table.component.ts:

export class TokenTableComponent implements OnInit {
  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;
  dataSource: TokenTableDataSource;

  pageSizeOptions = [5, 10, 20, 40];
  pageSize = this.pageSizeOptions[0];
  pageIndex = 0;
  tableLength = 0;

  ... colums definition

  ngOnInit(): void {
    this.dataSource = new TokenTableDataSource(this.paginator, this.sort);
  }

  public handlePage(pageEvent: PageEvent) {
    // do what?
  }
}

What's working: 什么工作:

  • The data is rendered correct (triggered with a button and via the ngxs-store) 数据呈现正确(通过按钮和ngxs-store触发)
  • I can sort the data 我可以对数据进行排序

What's not working: 什么不起作用:

  • On first data load the pageSize is ignored at all and all rows are displyed 在第一次数据加载时,将忽略pageSize并且显示所有行
  • When clicking sorting or a pagination element, the current selected pageSize is taken and this amount of rows is rendered. 单击排序或分页元素时,将获取当前选定的pageSize并呈现此行数。 What's strange to me is that this only works descending (given pageSize is 10 and I select 5 it results in 5 rows but once 5 is selected it's not possible to display more rows than 5 again) 令我感到奇怪的是,这只能降序(给定pageSize为10,我选择5,结果为5行,但一旦选择5,则不可能再次显示5行以上)

Requirements: 要求:

  • I like the idea to encapsulate all data manipulations behind TableDataSource.connect() so a solution like this where the fetching is done in the comonent is not desired. 我喜欢这个主意来封装之后的所有数据操作TableDataSource.connect()这样的解决方案像这样在取在comonent不希望完成。 Furthermore this doesn't have sorting implemented. 此外,这没有实施排序。
  • The app uses an ngxs-store , which is very similar to ngrx , so any solution involving this part is welcome. 该应用程序使用的ngxs-storengrx非常相似,因此欢迎涉及此部分的任何解决方案。
  • I haven't figured out what to do with pageEvents so my guess is that the solution is in the handlePage() method. 我还没弄清楚如何处理pageEvents所以我的猜测是解决方案是在handlePage()方法中。

Versions: 版本:

  • RxJS 6.3.x RxJS 6.3.x
  • Angular 7.x Angular 7.x
  • ngxs 3.3.x ngxs 3.3.x

I figured out how to setup a table for my requirements. 我想出了如何根据我的要求设置表格。 The main change is that I removed the Observable which fetches the data from the TableDataSource and introduced a DataService : 主要的变化是我删除了Observable,它从TableDataSource获取数据并引入了DataService

export class DataService {
  //the @Select is from ngxs but can be anything returning an Observable 
  @Select(TokenTableState.getTokenTableItems) private tokenTableItems$: Observable<TokenTableViewItem[]>;
  private initValue$: BehaviorSubject<TokenTableViewItem[]> = new BehaviorSubject<TokenTableViewItem[]>([]);

  getAllItems(): Observable<TokenTableViewItem[]> {
    const sources = [this.tokenTableItems$, this.initValue$];
    return merge(...sources);
  }
}

Basically that service gets the data from any Observable input and merges this in the getAllItems method with an initial value. 基本上,该服务从任何Observable输入获取数据,并在getAllItems方法中将其与初始值合并。

The Component has an instance of this service: Component有一个这个服务的实例:

private _dataService: DataService | null;

which it hands over to the TableDatasource in the load method: 它在load方法中移交给TableDatasource

private loadData(): any {
    this._dataService = new DataService();
    this.dataSource = new TokenTableDataSource(
      this._dataService,
      this.paginator,
      this.sort
    );
    fromEvent(this.filter.nativeElement, 'keyup').subscribe(() => {
      if (!this.dataSource) {
        return;
      }
      this.dataSource.filter = this.filter.nativeElement.value;
    });
  }

The reason I don't have a reference of the DataService in the TableDataSource is that the paginator in the Component needs the length of the table for rendering (seen below). 我在TableDataSource中没有DataService引用的原因是Component中的paginator需要表的长度进行渲染(见下文)。

The TableDataSource consumes the DataService like this: TableDataSource使用如下DataService

  • In the connect method it holds an array with possible data mutations: connect方法中,它包含一个包含可能的数据突变的数组:

     const dataMutations = [ this._dataChange, this._sort.sortChange, this._filterChange, this._paginator.page ]; 
  • The _dataChange member of the array gets it value by subscribing to the getAllItems method from our DataService : 数组的_dataChange成员通过从我们的DataService订阅getAllItems方法获取它的值:

     this._internalService.getAllItems().subscribe(data => { this._dataChange.next(data); }); 
  • The dataMutations are used like this to filter, sort and return the data which should be displayed: dataMutations用于过滤,排序和返回应显示的数据:

     return merge(...dataMutations).pipe( map(() => { // Filter data this.filteredData = this._dataChange.value .slice() .filter((item: TokenTableViewItem) => { const searchStr = (item.symbol + item.name).toLowerCase(); return searchStr.indexOf(this.filter.toLowerCase()) !== -1; }); // Sort filtered data const sortedData = this.getSortedData(this.filteredData.slice()); // Grab the page's slice of the filtered sorted data. this.renderedData = this.getPagedData(sortedData); return this.renderedData; }) ); 

The filterChange is defined in the local instance filterChange在本地实例中定义

_filterChange = new BehaviorSubject('');

while the pagination and sorting are triggered from outside via the constructor 分页排序是通过构造函数从外部触发的

constructor(
  public _internalService: DataService,
  public _paginator: MatPaginator,
  public _sort: MatSort
) {
  super();
  this._filterChange.subscribe(() => (this._paginator.pageIndex = 0));
}

I also found a solution for the pagination which is defined in the component.html like this: 我还找到了一个分区的解决方案,它在component.html中定义如下:

<mat-paginator #paginator
  [length]="dataSource.filteredData.length"
  [pageIndex]="pageIndex"
  [pageSize]="pageSize"
  [pageSizeOptions]="pageSizeOptions"
  [showFirstLastButtons]=true>
</mat-paginator>

and with the variables set in the component.ts : 并使用component.ts中设置的变量:

pageSizeOptions = [5, 10, 20, 40];
pageSize = this.pageSizeOptions[0];
pageIndex = 0;

The full code can be seen at this project and a live version of the table is used at whatsmytoken.com . 完整的代码可以在这个项目中看到,表的实时版本在whatsmytoken.com上使用

WOW! 哇!
just about the same time, I wrote an article about my Reactive DataSource, that can be easily extended for multiple data lists! 几乎在同一时间,我写了一篇关于我的Reactive DataSource的文章,可以很容易地扩展到多个数据列表! you can add optional and required mutators , accompanied of getter functions to collect the respective arguments and merge them in a REQuest object. 您可以添加可选必需的 mutator ,并附带getter函数来收集相应的参数并将它们合并到REQuest对象中。

I explained the overall stuff here: 我在这里解释了整体内容:
https://medium.com/@matheo/reactive-datasource-for-angular-1d869b0155f6 https://medium.com/@matheo/reactive-datasource-for-angular-1d869b0155f6

and I mounted a demo on StackBlitz too with a Github repo showing with simple commits, how simple is to set up a filtered/sorted/paginated list in a clean way. 我在StackBlitz上安装了一个演示程序,并使用Gi​​thub repo显示简单提交,以简洁的方式设置过滤/排序/分页列表是多么简单。

I hope you give me some feedback about my library, 我希望你能给我一些关于我图书馆的反馈,
and if you find it appealing, I can be sure to support you with your use cases too :) 如果你觉得它很有吸引力,我可以肯定也会用你的用例支持你:)

Happy coding! 快乐的编码!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM