1

I have couple of components which displays data in table form . I want to refactor the code into reusable data table component so that other components can use it instead of duplicating the table in every component.But I am not sure how to create a reusable Angular material data table. Can anyone pls help me with this.

<div class="example-container" #TABLE> 

  <mat-table #table [dataSource]="dataSource" matSort matSortActive="locationName" matSortDirection="asc" matSortDisableClear>
    <ng-container matColumnDef="locationName">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Location Name </mat-header-cell>
      <mat-cell *matCellDef="let location"> {{location.locationName}} </mat-cell>
    </ng-container>
    <ng-container matColumnDef="address">
      <mat-header-cell *matHeaderCellDef>Address </mat-header-cell>
      <mat-cell *matCellDef="let location"> {{location.address}} </mat-cell>
    </ng-container>
    <ng-container matColumnDef="city">
      <mat-header-cell *matHeaderCellDef mat-sort-header> City </mat-header-cell>
      <mat-cell *matCellDef="let location"> {{location.city}} </mat-cell>
    </ng-container>
    <ng-container matColumnDef="country">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Country </mat-header-cell>
      <mat-cell *matCellDef="let location"> {{location.country}} </mat-cell>
    </ng-container>
    <ng-container matColumnDef="zipcode">
      <mat-header-cell *matHeaderCellDef>ZipCode </mat-header-cell>
      <mat-cell *matCellDef="let location"> {{location.zipcode}} </mat-cell>
    </ng-container>
    <ng-container matColumnDef="phone">
      <mat-header-cell *matHeaderCellDef>Phone </mat-header-cell>
      <mat-cell *matCellDef="let location"> {{location.phone}} </mat-cell>
    </ng-container>
    <ng-container matColumnDef="timezone">
      <mat-header-cell *matHeaderCellDef> TimeZone </mat-header-cell>
      <mat-cell *matCellDef="let location"> {{location.timezone}} </mat-cell>
    </ng-container>
    <ng-container matColumnDef="action">
      <mat-header-cell *matHeaderCellDef> Action </mat-header-cell>
      <mat-cell *matCellDef="let location">
      <a [routerLink]="['/admin/location/edit/',location.locationID]" class="btn Action-Tab">Edit</a>&nbsp;&nbsp;
      <a class="btn Action-Tab" (click)="deleteLocation(location,location.locationID)">Delete</a>
        </mat-cell>
    </ng-container>
    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let location; columns: displayedColumns;">
    </mat-row>
  </mat-table>

  <mat-paginator [pageSizeOptions]="[10, 20, 50,100]"></mat-paginator>
2
  • "Reusable" is a broad word. Angular Material's Table Component is a reusable component. Do you want your "CustomTableComponent" to display a variety of data structures ? Commented May 29, 2018 at 16:57
  • I want to create a shared component and use it in different components instead of creating mat-table in every component Commented May 29, 2018 at 17:29

1 Answer 1

1

You can use interfaces and abstract classes, generics and share the .html file with the columns. This is a small example that I used with angle "@ angular / core": "4.2.4", I used it to display data:

File: shared-bce-read-table.interface.ts

import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {FooterIdentifiableTotalQuantityInterface} from '../../entities/common/product-table-editable/footer-identifiable-total-quantity.interface';

/**
 * Shared interface to read data
 */
export interface SharedBceReadTableInterface<T extends FooterIdentifiableTotalQuantityInterface>{

    displayedColumns: string[];
    displayedColumnsFooter: string[];
    dataChange: BehaviorSubject<T[]>;
    data(): T[];
    dataSource: SharedBceReadDataSource<T>
}

I use an interface to identify the generic T.

File: footer-identifiable-total-quantity.interface.ts

/**
 * Interface to management total quantity sum in editing tables.
 */
export interface FooterIdentifiableTotalQuantityInterface {

    isFooterRow: boolean;

    getBalance(): number;

    getAmountBalance(): number;
}

Now a generic datasource.

File: shared-bce-read-data-source.component.ts

import {MatPaginator, MatSort} from '@angular/material';
import {Observable} from 'rxjs/Observable';
import {DataSource} from '@angular/cdk/collections';
import {SharedBceReadTableInterface} from './shared-bce-read-table.interface';
import {FooterIdentifiableTotalQuantityInterface} from '../../entities/common/product-table-editable/footer-identifiable-total-quantity.interface';

/**
 * SharedRead, for this auxiliary component, of elements for sorter must be equals to identifier columns table.
 */
export class SharedBceReadDataSource<T extends FooterIdentifiableTotalQuantityInterface> extends DataSource<T> {
    constructor(private _table: SharedBceReadTableInterface<T>, private _sort: MatSort, private _paginator: MatPaginator) {
        super();
    }

    connect(): Observable<T[]> {
        const displayDataChanges = [
            this._table.dataChange,
            this._sort.sortChange,
            this._paginator.page
        ];

        return Observable.merge(...displayDataChanges).map(() => {
            const data = this.getSortedData();

            const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
            return data.splice(startIndex, this._paginator.pageSize);
        });
    }

    disconnect() {}

    getSortedData(): T[] {
        const data = this._table.data().slice();
        if (!this._sort.active || this._sort.direction === '') { return data; }

        return data.sort((a, b) => {
            const propertyA: number|string = a[this._sort.active];
            const propertyB: number|string = b[this._sort.active];

            const valueA = isNaN(+propertyA) ? propertyA : +propertyA;
            const valueB = isNaN(+propertyB) ? propertyB : +propertyB;

            return (valueA < valueB ? -1 : 1) * (this._sort.direction === 'asc' ? 1 : -1);
        });
    }
}

Now we can implement these classes every component where we expect to display data.

import {Component, Inject, OnInit, ViewChild} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef, MatPaginator, MatSort} from '@angular/material';
import {JhiAlertService, JhiEventManager} from 'ng-jhipster';
import {LoadingOverlayService} from '../../../core/loading-overlay/loading-overlay.service';
import {
    DEFAULT_ID_SORT,
    ITEMS_PER_PAGE,
    PAGE_SIZE_OPTIONS,
    ResponseWrapper,
    SharedBceReadDataSource,
    SharedBceReadTableInterface,
    START_PAGE
} from '../../../shared';
import {ProductOrderBce} from '../../product-order/product-order-bce.model';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {ProductOrderBceService} from '../../product-order';

@Component({
    selector: 'jhi-order-detail-product-purchase-order',
    templateUrl: 'order-detail-product-purchase-order.html'
})
export class OrderDetailProductPurchaseOrderComponent implements OnInit, SharedBceReadTableInterface<ProductOrderBce> {

    displayedColumns = ['purchaseOrderFolio', 'productKey', 'description', 'unitMeasureKey', 'quantity', 'unitPrice', 'amount'];
    displayedColumnsFooter = [];
    dataChange: BehaviorSubject<ProductOrderBce[]> = new BehaviorSubject<ProductOrderBce[]>([]);
    authorities: any[];
    dataSource: SharedBceReadDataSource<ProductOrderBce> = null;
    pageSizeOptions = PAGE_SIZE_OPTIONS;
    itemsPerPage = ITEMS_PER_PAGE;
    totalAmount = 0;
    totalQuantity = 0;

    @ViewChild(MatSort) sortComponent: MatSort;
    @ViewChild(MatPaginator) paginatorComponent: MatPaginator;

    constructor(
        private dialogRef: MatDialogRef<OrderDetailProductPurchaseOrderComponent>,
        @Inject(MAT_DIALOG_DATA) public dataDialog: any,
        private service: ProductOrderBceService,
        private eventManager: JhiEventManager,
        private alertService: JhiAlertService,
        protected loadingService: LoadingOverlayService
    ) {}

    ngOnInit() {
        this.authorities = ['ROLE_USER', 'ROLE_ADMIN'];
        this.dataChange.subscribe((data) => {
            data.forEach((row) => {
                this.totalQuantity += row.quantity;
                this.totalAmount += row.amount;
            });
        }, (error) => {
            this.totalAmount = 0;
            this.totalQuantity = 0;
        });
        this.dataSource = new SharedBceReadDataSource<ProductOrderBce>(this, this.sortComponent, this.paginatorComponent);
        this.loadingService.startLoading();
        this.service.searchByOrigenOrder({page: START_PAGE, size: ITEMS_PER_PAGE, sort: DEFAULT_ID_SORT,
            orderId: this.dataDialog.orderId, orderVersion: this.dataDialog.orderVersion, productId: this.dataDialog.productId}).subscribe(
            (res: ResponseWrapper) => {
                this.dataChange.next(res.json.items);
                this.loadingService.stopLoading();
            },
            (res: ResponseWrapper) => {
                this.dataChange.next([]);
                this.onError(res.json());
                this.loadingService.stopLoading();
            }
        );
    }

    onError(error) {
        this.alertService.error(error.message, null, null);
    }

    onCloseCancel() {
        this.dialogRef.close();
    }

    public data(): ProductOrderBce[] {
        return this.dataChange.value;
    }

}

Product order must implement the FooterIdentifiableTotalQuantityInterface interface to be compatible with the table.

product-order-bce.model.ts

import { BaseEntity } from './../../shared';
import {EventEmitter} from '@angular/core';
import {IdentifiableProductI, ProductEditableI} from '../common';
import {FooterIdentifiableTotalQuantityInterface} from '../common/product-table-editable/footer-identifiable-total-quantity.interface';

export class ProductOrderBce implements BaseEntity, IdentifiableProductI, FooterIdentifiableTotalQuantityInterface {
    id: number = null;
    isFooterRow = false;

    constructor(
        id?: number,
        public quantity?: number,
        public balance?: number,
        public unitPrice?: number,
        public amount?: number,
        public amountBalance?: number,
        public totalRecivedQuantity?: number,
        public productId?: number,
        public unitMeasureId?: number,
        public productsOrderId?: number,
        public productsOrderVersion?: number,
        public productsOrderFolio?: string,
        public productDescription?: string,
        public unitMeasureDescription?: string,
        public unitMeasureKey?: string,
        public productKey?: string,
        public origenProductOrderId?: number,
        public origenProductOrderQuantity?: number,
        public origenProductOrderReceivedQuantity?: number
    ) {
        this.quantity = 0;
        this.unitPrice = 0;
        this.amount = 0;
        this.totalRecivedQuantity = 0;
        this.id = id;
    }

    getId(): any {
        return this.id;
    }

    getProductKey(): string {
        return this.productKey;
    }

    getProductDescription(): string {
        return this.productDescription;
    }

    getBalance() {
        return this.quantity - this.totalRecivedQuantity;
    }

    getAmountBalance() {
        return this.getBalance() * this.unitPrice;
    }

}

If you observe the logic of displaying the table it is implemented, you would only have to include the file for each case. html, complete the displayed Columns, I used material-angular here. I hope and this can help you, the same thing happened to me and these classes I use to show detailed views of any object.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.