The wrapper component for ngx-datatable

Some introduction:

We are currently developing an application based on Angular2, which is pretty heavy. To show this data, we decided to try ngx-datatables . Many components will be required, showing data in grids. We've added a custom footer template, as well as a kind of custom header showing the page size selector using the element <select>.

The number of markup lines grew quite a lot, so we would like to move the definition <ngx-datatable>with a header and footer to a separate component of the grid. Now we would like to reuse this component, allowing the developer to use the grid to simply define the columns in the markup, to have full flexibility when it comes to the contents of the column.

The idea is to have a commonly used grid component that only queries the data as input and displays it. Typical functionality (sorting and staking on the server side) in the grid should exist only once in the grid component. A component that uses a grid component should simply provide the data that the grid components signed and what it is.

What we have at the moment:

A common grid element with a grid selector defined in a .ts file

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

Concrete mesh

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

We tried to use <ng-content></ng-content>for columns, but with no luck, the grid just does not display, I think, because the columns are not defined.

Is there a way not to repeat the same code to define the grid over and over and implement some kind of shell that does general markup?

Grateful for any input. Thanks in advance!

Update

We managed to do this through the .ts file and ng-templatein the markup, but we would prefer to define the columns only in the markup. Anyone any idea?

+4
source share
1 answer

We decided to go with a solution having the column definitions in the .ts file.

Here is our solution:

"", .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) / (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;
    }
}

, - -:)

+7

Source: https://habr.com/ru/post/1684210/


All Articles