ngx-datatable 的通用 'wrapper' 组件

Common 'wrapper' component for ngx-datatable

一些介绍:

我们目前正在开发一个基于 Angular2 的应用程序,该应用程序的数据量很大。为了显示此数据,我们决定尝试 ngx-datatables。 将需要大量组件在网格中显示数据。我们添加了一个定制的页脚模板以及一种使用 <select> 元素显示页面大小选择器的定制页眉。

标记行的数量增加了很多,因此我们想将带有页眉和页脚的定义 <ngx-datatable> 移动到单独的网格组件中。现在我们希望通过允许使用网格的开发人员简单地定义标记中的列来重用该组件,以便在涉及列内容时具有完全的灵活性。

这个想法是有一个常用的网格组件,它只要求数据作为输入并呈现它。网格中的典型功能(服务器端排序和分页)在网格组件中应该只存在一次。 使用grid组件的组件只要提供grid组件订阅的数据即可。

我们现在有:

带选择器的公共网格组件 'grid' 在 .ts 文件中定义

<div class="gridheader">
    ... page size selector and other elements ...
</div>
<ngx-datatable
  class="material"
  [columnMode]="'force'"
  [rows]="data"
  [headerHeight]="'auto'"
  [footerHeight]="'auto'"
  [rowHeight]="'auto'"
  [externalPaging]="true"
  [externalSorting]="true"
  [count]="totalElements"
  [offset]="currentPageNumber"
  [limit]="pageSize"
  [loadingIndicator]="isLoading"
  (page)='loadPage($event)'
  (sort)="onSort($event)">

  <ng-content>
  </ng-content>

  <ngx-datatable-footer>
    <ng-template 
      ngx-datatable-footer-template 
      let-rowCount="rowCount"
      let-pageSize="pageSize"
      let-selectedCount="selectedCount"
      let-curPage="curPage"
      let-offset="offset">
      <div style="padding: 5px 10px">
        <div>
          <strong>Summary</strong>: Gender: Female
        </div>
        <hr style="width:100%" />
        <div>
          Rows: {{rowCount}} |
          Size: {{pageSize}} |
          Current: {{curPage}} |
          Offset: {{offset}}
        </div>
      </div>
    </ng-template>
  </ngx-datatable-footer>

</ngx-datatable>

特定网格

<grid (onFetchDataRequired)="fetchDataRequired($event)">

  <ngx-datatable-column prop="Id" name=" ">
    <ng-template let-value="value" ngx-datatable-cell-template>
      <a [routerLink]="['edit', value]" class="btn btn-sm btn-outline-primary">
        <i class="fa fa-pencil" aria-hidden="true"></i>
      </a>
    </ng-template>
  </ngx-datatable-column>
  <ngx-datatable-column name="CreatedBy" prop="CreatedBy">
    <ng-template let-value="value" ngx-datatable-cell-template>
      {{value}}
    </ng-template>
  </ngx-datatable-column>

  ... even more columns ...   

</grid>

我们尝试对列使用 <ng-content></ng-content> 但没有成功,网格只是没有呈现,我猜是因为没有定义任何列。

有没有办法不一遍又一遍地重复网格定义的相同代码,并实现某种处理通用标记的包装器?

感谢任何输入。 提前致谢!

更新

我们设法通过 .ts 文件和标记中的 ng-template 来实现,但我们更愿意仅在标记中定义列。 有人知道吗?

我们决定采用在 .ts 文件中包含列定义的解决方案。

这是我们的解决方案:

带选择器的公共网格组件 'grid' 在 .ts 文件中定义

grid.component.html

<div class="ngx-datatable material">
    <div class="datatable-footer datatable-footer-inner">
        <div class="page-count">
            Show
            <select (change)="onLimitChange($event.target.value)" class="page-limit">
                <option
                    *ngFor="let option of pageLimitOptions"
                    [value]="option.value"
                    [selected]="option.value == currentPageLimit">
                    {{option.value}}
                </option>
            </select>
            per page
        </div>
    </div>
    <ngx-datatable
        class="material striped"
        [columns]="columns"
        [columnMode]="'force'"
        [rows]="gridModel.Data"
        [headerHeight]="'auto'"
        [footerHeight]="'auto'"
        [rowHeight]="'auto'"
        [externalPaging]="true"
        [externalSorting]="true"
        [count]="gridModel?.TotalElements"
        [offset]="gridModel?.CurrentPageNumber"
        [limit]="gridModel?.PageSize"
        [loadingIndicator]="isLoading"
        (page)='loadPage($event)'
        (sort)="onSort($event)">
    </ngx-datatable>
</div>
<app-spinner [isRunning]="isLoading"></app-spinner>
<ng-template #emptyTemplate let-row="row" let-value="value"></ng-template>
<ng-template #idAnchorEditTemplate let-row="row" let-value="value">
    <a [routerLink]="['edit', value]" class="btn btn-sm btn-outline-primary">
        <i class="fa fa-pencil" aria-hidden="true"></i>
    </a>
</ng-template>
<ng-template #dateTemplate let-row="row" let-value="value">
    {{value | date:'dd.MM.yyyy' }}
</ng-template>
<ng-template #dateTimeTemplate let-row="row" let-value="value">
    {{value | date:'dd.MM.yyyy HH:mm:ss' }}
</ng-template>

grid.component.ts

import { Component, Injectable, Input, Output, OnInit, OnDestroy, EventEmitter, ViewChild, TemplateRef } from '@angular/core';
import { NgxDatatableModule, DatatableComponent } from '@swimlane/ngx-datatable';
import { TableColumn } from '@swimlane/ngx-datatable/release/types';

import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/Rx';

import { GridModel } from '../grid/grid-model.model'

@Injectable()
@Component({
    selector: 'grid',
    templateUrl: './grid.component.html'
})
export class GridComponent<T> implements OnInit, OnDestroy {
    @Input()
    columns: TableColumn[];

    private _gridModelInput = new BehaviorSubject<GridModel<T>>(undefined);

    @ViewChild('emptyTemplate') 
    public emptyTemplate: TemplateRef<any>;

    @ViewChild('idAnchorEditTemplate') 
    public idAnchorEditTemplate: TemplateRef<any>;

    @ViewChild('dateTemplate') 
    public dateTemplate: TemplateRef<any>;

    @ViewChild('dateTimeTemplate') 
    public dateTimeTemplate: TemplateRef<any>;

    // change data to use getter and setter
    @Input()
    set gridModelInput(value) {
        // set the latest value for _data BehaviorSubject
        if (value !== undefined) {
            this._gridModelInput.next(value);
        }
    };

    get gridModelInput() {
        // get the latest value from _data BehaviorSubject
        return this._gridModelInput.getValue();
    }

    @Output()
    onFetchDataRequired = new EventEmitter<GridModel<T>>();

    private gridModel: GridModel<T>;
    private isLoading: boolean = false;
    private currentPageLimit: number = 0;
    private pageLimitOptions = [
        {value: 10},
        {value: 25},
        {value: 50},
        {value: 100},
    ];

    constructor() {
    }

    ngOnInit(): void {
        this.gridModel = new GridModel<T>();

        this._gridModelInput.subscribe(gridModel => {
            this.gridModel = gridModel;
            this.isLoading = false;
        }, err => console.log(err));

        this.loadPage();
    }

    protected loadPage(pageEvent = {offset: 0}){
        this.gridModel.CurrentPageNumber = pageEvent.offset;
        this.onFetchDataRequired.emit(this.gridModel);
        this.isLoading = true;
    }

    protected onSort(event) {
        if (this.gridModel.SortBy != event.sorts[0].prop) {
            //this means we are sorting on a new column
            //so we need to return the paging to the first page
            this.gridModel.CurrentPageNumber = 0;            
        }

        this.gridModel.SortBy = event.sorts[0].prop;
        this.gridModel.SortDir = event.sorts[0].dir;

        this.loadPage();
    }

    public onLimitChange(limit: any): void {
        this.gridModel.PageSize = this.currentPageLimit = parseInt(limit, 10);
        this.gridModel.CurrentPageNumber = 0;
        this.loadPage();
    }

    ngOnDestroy(): void {
        this._gridModelInput.unsubscribe();
    }
}

此网格组件包装器的用法

数据-grid.component.html

<grid
    (onFetchDataRequired)="fetchDataRequired($event)"
    [gridModelInput]="gridModel">
</grid>

数据-grid.component.ts

import { Component, OnInit, ViewChild, TemplateRef } from '@angular/core';

import { GridComponent } from '../shared/grid/grid.component';
import { GridModel } from '../shared/grid/grid-model.model';
import { DataGridRowModel } from './data-gridrow.model';
import { DataFetchService } from './data-fetch.service';

@Component({
  templateUrl: 'data-grid.component.html'
})
export class DataGridComponent implements OnInit {
  @ViewChild(GridComponent) grid: GridComponent<DataGridRowModel>;
  gridModel: GridModel<DataGridRowModel> = new GridModel<DataGridRowModel>('DateCreated', 'desc');

  ngOnInit(): void {
    this.grid.columns = [
      { prop: 'Id', cellTemplate: this.grid.idAnchorEditTemplate, headerTemplate: this.grid.emptyTemplate }
      , { prop: 'CreatedBy' }
      , { prop: 'DateCreated', cellTemplate: this.grid.dateTimeTemplate, name: 'Created Date' }
    ];
  }

  constructor(private dataFetchService: DataFetchService) {
  }

  fetchDataRequired(gridModel: GridModel<DataGridRowModel>) {
    this.dataFetchService
      .getSortedPagedResults(gridModel)
      .subscribe(gridModelResponse => {
        this.gridModel = gridModelResponse;
    });
  }
}

它最酷的地方在于,它预定义了常用的模板,例如对于 ID 列 (idAnchorEditTemplate)、日期列 (dateTemplate) 或 date/time 列 (dateTimeTemplate)。 这允许在单个文件中维护在整个应用程序中使用的列模板。

需要的另一种类型是 GridModel:

export class GridModel<T> {
    PageSize: number;
    TotalElements: number;
    TotalPages: number;
    CurrentPageNumber: number;
    SortBy: string;
    SortDir: string;
    Data: Array<T>;

    constructor(defaultSortBy: string = 'Id', defaultSortDir: string = 'asc') {
        this.PageSize = 10;
        this.TotalElements = 0;
        this.TotalPages = 0;
        this.CurrentPageNumber = 0;
        this.Data = new Array<T>();

        this.SortBy = defaultSortBy;
        this.SortDir = defaultSortDir;
    }
}

也许有一天有人会从中受益:)