src/app/components/table/table.component.ts
Displays a table with provided data
selector | ccf-table |
styleUrls | ./table.component.scss |
templateUrl | ./table.component.html |
Properties |
|
Methods |
Inputs |
Accessors |
additionalColumnsData | |
Type : string[]
|
|
Column definitions of additional columns to be displayed |
additionalHeaders | |
Type : ExtraHeader[]
|
|
Details of additional columns to be displayed |
cellHeaders | |
Type : ExtraHeader[]
|
|
Details of cell columns to be displayed |
cellHeadersData | |
Type : string[]
|
|
Column definitions of cell columns to be displayed |
columns | |
Type : HeaderData[]
|
|
Default value : []
|
|
Details of the columns to be displayed |
displayedColumns | |
Type : string[]
|
|
Default value : []
|
|
Column definitions of the columns to be displayed |
isOrgan | |
Type : boolean
|
|
Default value : false
|
|
Flag to check if column is of organ |
isTotal | |
Type : boolean
|
|
Default value : false
|
|
Flag to show/hide the total below the table |
typeCount | |
Type : TableData[]
|
|
Sets the data to the table datasource |
formatData | ||||
formatData(value)
|
||||
Return's 'no data' if column cell is null
Parameters :
Returns :
string
|
getAlignmentClass | ||||||
getAlignmentClass(column: HeaderData)
|
||||||
Returns an alignment class for the column
Parameters :
Returns :
string
|
getTotal | ||||||
getTotal(id: string)
|
||||||
Returns sum of numbers in a column
Parameters :
Returns :
any
|
isNumericColumn | ||||||
isNumericColumn(column: string)
|
||||||
Returns if column has atleast one number
Parameters :
Returns :
boolean
|
Readonly dataSource |
Default value : new MatTableDataSource<TableData>([])
|
Datasource to store table data |
sort | ||||||
setsort(value: MatSort)
|
||||||
Sorts the current selected data
Parameters :
Returns :
void
|
typeCount | ||||||
settypeCount(data: TableData[])
|
||||||
Sets the data to the table datasource
Parameters :
Returns :
void
|
firstColumnId |
getfirstColumnId()
|
Returns the column definition of first column
Returns :
string
|
import { Component, Input, ViewChild } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { ExtraHeader, HeaderData } from './header';
import { TableData } from './table';
/** Displays a table with provided data */
@Component({
selector: 'ccf-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss'],
})
export class TableComponent {
/** Sorts the current selected data */
@ViewChild(MatSort, { static: true })
set sort(value: MatSort) {
this.dataSource.sort = value;
}
/** Flag to check if column is of organ */
@Input() isOrgan = false;
/** Sets the data to the table datasource */
@Input()
set typeCount(data: TableData[]) {
this.dataSource.data = data;
}
/** Column definitions of the columns to be displayed */
@Input() displayedColumns: string[] = [];
/** Details of the columns to be displayed */
@Input() columns: HeaderData[] = [];
/** Flag to show/hide the total below the table */
@Input() isTotal = false;
/** Details of additional columns to be displayed */
@Input() additionalHeaders?: ExtraHeader[];
/** Column definitions of additional columns to be displayed */
@Input() additionalColumnsData?: string[];
/** Details of cell columns to be displayed */
@Input() cellHeaders?: ExtraHeader[];
/** Column definitions of cell columns to be displayed */
@Input() cellHeadersData?: string[];
/** Datasource to store table data */
readonly dataSource = new MatTableDataSource<TableData>([]);
/** Returns the column definition of first column */
get firstColumnId(): string {
return this.columns[0]?.columnDef ?? '';
}
/** Returns sum of numbers in a column */
getTotal(id: string) {
const sum = this.dataSource.data
.filter((entry) => typeof entry[id] === 'number')
.reduce((acc, entry) => acc + (entry[id] as number), 0);
return sum.toLocaleString();
}
/** Returns if column has atleast one number */
isNumericColumn(column: string): boolean {
if (column === 'table_version') {
return false;
}
let hasAtLeastOneNumber = false;
for (const { [column]: value } of this.dataSource.data) {
if (value === null) {
continue;
} else if (typeof value !== 'number') {
return false;
}
hasAtLeastOneNumber = true;
}
return hasAtLeastOneNumber;
}
/** Returns an alignment class for the column */
getAlignmentClass(column: HeaderData): string {
return `alignment-${column.alignment ?? 'default'}`;
}
/** Return's 'no data' if column cell is null */
formatData(value: unknown): string {
return value !== null ? `${value}` : 'no data';
}
}
<table
mat-table
[dataSource]="dataSource"
matSort
[matSortActive]="firstColumnId"
matSortDirection=""
class="mat-elevation-z8 data-table"
>
<ng-container *ngIf="cellHeaders?.length">
<ng-container *ngFor="let cells of cellHeaders" [matColumnDef]="cells.columnDef">
<th
mat-header-cell
*matHeaderCellDef
[attr.colspan]="cells.colspan ?? 1"
[attr.rowspan]="cells.rowspan ?? 1"
class="table-title additional-header"
>
{{ cells.header }}
</th>
</ng-container>
</ng-container>
<ng-container *ngIf="additionalHeaders?.length">
<ng-container *ngFor="let addition of additionalHeaders" [matColumnDef]="addition.columnDef">
<th
mat-header-cell
*matHeaderCellDef
[attr.colspan]="addition.colspan ?? 1"
[attr.rowspan]="addition.rowspan ?? 1"
class="table-title additional-header"
>
{{ addition.header }}
</th>
</ng-container>
</ng-container>
<ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
<ng-container *ngIf="column.sorting ?? true; else noSortHeader">
<th mat-header-cell *matHeaderCellDef class="table-title" mat-sort-header [class]="getAlignmentClass(column)">
{{ column.header }}
</th>
</ng-container>
<ng-template #noSortHeader>
<th mat-header-cell *matHeaderCellDef class="table-title">
{{ column.header }}
</th>
</ng-template>
<td
mat-cell
*matCellDef="let row"
class="table-cell"
[innerHtml]="formatData(column.cell(row))"
[class.transform]="isOrgan"
[class]="getAlignmentClass(column)"
></td>
<ng-container *ngIf="isTotal">
<td mat-footer-cell *matFooterCellDef class="table-footer">
<ng-container *ngIf="isNumericColumn(column.columnDef); else totalLabel">
{{ getTotal(column.columnDef) }}
</ng-container>
<ng-template #totalLabel>
<ng-container *ngIf="column.isTotalRequired">Totals</ng-container>
</ng-template>
</td>
</ng-container>
</ng-container>
<ng-container *ngIf="cellHeaders?.length">
<tr mat-header-row *matHeaderRowDef="cellHeadersData"></tr>
</ng-container>
<ng-container *ngIf="additionalHeaders?.length">
<tr mat-header-row *matHeaderRowDef="additionalColumnsData"></tr>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns" class="row-one"></tr>
<div class="first-column">
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</div>
<div *ngIf="isTotal">
<tr mat-footer-row *matFooterRowDef="displayedColumns"></tr>
</div>
</table>
./table.component.scss
$text-color: #212121;
$background-color: #ffffff;
$border-color: #e0e0e0;
@mixin default-font($weight: 500) {
font-family: 'Inter';
font-style: normal;
font-weight: $weight;
font-size: 0.875rem;
line-height: 1.5rem;
letter-spacing: 0.005rem;
}
@mixin text-alignment($type, $text-align) {
$text-align-to-justify-content: (
start: start,
left: start,
end: end,
right: end,
center: center,
);
$text-align-to-padding-side: (
start: left,
left: left,
end: right,
right: right,
center: '',
);
$text-align-to-text-align: (
start: left,
left: left,
end: right,
right: right,
center: '',
);
$justify-content: map-get($text-align-to-justify-content, $text-align);
$padding-side: map-get($text-align-to-padding-side, $text-align);
$text-align: map-get($text-align-to-text-align, $text-align);
.alignment-#{$type} {
text-align: $text-align;
@if $padding-side != '' {
padding-#{$padding-side}: 1rem;
}
}
::ng-deep th.alignment-#{$type} > * {
justify-content: $justify-content;
.mat-sort-header-content {
text-align: $text-align;
}
}
}
:host {
display: block;
width: 100%;
overflow-x: auto;
.data-table {
table-layout: fixed;
min-width: 100%;
background-color: $background-color;
border: 0.063rem solid $border-color;
box-shadow: 0rem 0.125rem 0.063rem rgba(0, 0, 0, 0.16);
.table-title,
.table-cell,
.table-footer {
@include default-font();
color: $text-color;
text-overflow: ellipsis;
white-space: normal;
}
.table-title {
vertical-align: text-top;
padding: 1rem;
max-width: 150px;
}
td.mat-footer-cell:first-of-type {
padding-left: 1rem;
}
.table-cell {
font-weight: 300;
box-sizing: border-box;
}
.table-footer:not(:first-child) {
text-align: right;
box-sizing: border-box;
white-space: unset;
padding-right: 1rem;
}
td.mat-cell:last-of-type {
box-sizing: border-box;
padding-right: 1rem;
}
td.mat-footer-cell:last-of-type {
box-sizing: border-box;
padding-right: 1rem;
}
@include text-alignment(default, right);
@include text-alignment(start, start);
@include text-alignment(end, end);
@include text-alignment(center, center);
::ng-deep .mat-sort-header-arrow {
color: #212121 !important;
&:hover {
transform: rotate(180deg) !important;
color: #ebebeb !important;
}
}
.additional-header {
text-align: center;
}
::ng-deep .mat-sort-header-container:not(.mat-sort-header-sorted) .mat-sort-header-arrow {
opacity: 0.54 !important;
transform: rotate(180deg) !important;
color: #ebebeb !important;
size: 3rem;
&:hover {
color: #8b8383 !important;
}
}
::ng-deep .mat-sort-header-content {
white-space: normal;
}
::ng-deep .mat-column-csv {
white-space: nowrap !important;
align-items: center;
}
::ng-deep td.mat-column-csv {
text-align: left !important;
padding-left: 1rem;
padding-right: unset !important;
}
::ng-deep .mat-column-Organ {
min-width: 12rem;
}
}
}