【问题标题】:Reactive Angular Material Data Table反应角材料数据表
【发布时间】:2019-05-14 13:23:39
【问题描述】:

我用这个ng generate @angular/material:material-table 命令创建了一个角度材料数据表,它给了我以下文件结构:

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

这里的想法是在table-datasource.ts 中完成所有的获取、排序和分页。默认情况下,数据被放置在table-datasource.ts 内的一个数组中,但在我的例子中,它来自一个公开数组的Observable 的ngxs-store。 Atm 我有以下实现:

table-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:

<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:

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?
  }
}

工作原理:

  • 数据呈现正确(通过按钮和 ngxs-store 触发)
  • 我可以对数据进行排序

什么不起作用:

  • 在第一次加载数据时,pageSize 被完全忽略,所有行都被显示
  • 单击排序或分页元素时,将采用当前选定的pageSize 并呈现此数量的行。对我来说奇怪的是,这只适用于降序(假设pageSize 为 10,我选择 5,它会产生 5 行,但一旦选择 5,就不可能再显示超过 5 行)

要求:

  • 我喜欢将所有数据操作封装在TableDataSource.connect() 后面的想法,因此不需要像this 这样的解决方案,其中提取在comonent 中完成。此外,这没有实现排序。
  • 该应用使用了一个 ngxs-store,它与 ngrx 非常相似,因此欢迎任何涉及这部分的解决方案。
  • 我还没有弄清楚如何处理 pageEvents 所以我的猜测是解决方案在 handlePage() 方法中。

版本:

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

【问题讨论】:

    标签: javascript angular rxjs angular-material


    【解决方案1】:

    哇!
    几乎在同一时间,我写了一篇关于我的 Reactive DataSource 的文章,它可以很容易地扩展到多个数据列表!您可以添加 optional 和 required mutators,并附带 getter 函数来收集各自的参数并将它们合并到一个请求对象。

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

    我也在 StackBlitz 上安装了一个演示 带有显示简单提交的 Github 存储库, 以干净的方式设置过滤/排序/分页列表是多么简单。

    我希望你能给我一些关于我的图书馆的反馈,
    如果你觉得它很吸引人,我也可以确保用你的用例来支持你:)

    编码愉快!

    【讨论】:

      【解决方案2】:

      我想出了如何根据我的要求设置表格。主要变化是我删除了从 TableDataSource 获取数据的 Observable,并引入了 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);
        }
      }
      

      基本上,该服务从任何 Observable 输入中获取数据,并在 getAllItems 方法中将其与初始值合并。

      组件有一个该服务的实例:

      private _dataService: DataService | null;

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

      TableDataSource中没有DataService的引用的原因是Component中的分页器需要长度为用于渲染的表格(见下文)。

      TableDataSource 像这样使用 DataService:

      • 在 connect 方法中,它包含一个可能存在数据突变的数组:

        const dataMutations = [
          this._dataChange,
          this._sort.sortChange,
          this._filterChange,
          this._paginator.page
        ];
        
      • 数组的 _dataChange 成员通过从我们的 DataService 订阅 getAllItems 方法来获取它的值:

        this._internalService.getAllItems().subscribe(data => {
          this._dataChange.next(data);
        });
        
      • 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;
          })
        );
        

      filterChange在本地实例中定义

      _filterChange = new BehaviorSubject('');

      分页和排序是通过构造函数从外部触发的

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

      我还找到了一个在 component.html 中定义的分页解决方案,如下所示:

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

      并使用 component.ts 中设置的变量:

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

      完整代码可在this 项目中查看,该表的实时版本在whatsmytoken.com 使用。

      【讨论】:

        猜你喜欢
        • 2022-06-30
        • 1970-01-01
        • 2020-04-30
        • 2021-01-11
        • 1970-01-01
        • 1970-01-01
        • 2020-10-08
        相关资源
        最近更新 更多