Angular Smart Table provides a full set of features for displaying and manipulating tabular data. It allows you to easily create dynamic and interactive tables with features such as sorting, filtering, pagination, and searching. Angular Smart Table Component (DataTable) makes it easy to work with large datasets, and it is widely used in a variety of applications, including web-based applications, e-commerce sites, and more.
Available in Other JavaScript Frameworks
CoreUI Angular Smart Table Component (DataTable) is also available for React and Vue. Explore framework-specific implementations below:
Features
- Filter items by one or all columns
- Sort items by column
- Integrated with CPagination component by default
- Customize style of specific rows, columns and cells
- Customize display of columns
- Load with initial filters and sorter state
- Loading state visualization
- Default header labels generation based on column names
Examples
Basic usage
import { NgClass } from '@angular/common';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import usersData from './data';
import {
AlignDirective,
BadgeComponent,
ButtonDirective,
CardBodyComponent,
CardComponent,
CardFooterComponent,
ColDirective,
CollapseDirective,
IColumn,
IItem,
SmartTableComponent,
TableActiveDirective,
TableColorDirective,
TemplateIdDirective
} from '@coreui/angular';
@Component({
selector: 'docs-smart-table-example',
templateUrl: './smart-table-example.component.html',
standalone: true,
changeDetection: ChangeDetectionStrategy.Eager,
imports: [
AlignDirective,
BadgeComponent,
ButtonDirective,
CardBodyComponent,
CardComponent,
CardFooterComponent,
ColDirective,
CollapseDirective,
NgClass,
SmartTableComponent,
TableActiveDirective,
TableColorDirective,
TemplateIdDirective
]
})
export class SmartTableExampleComponent {
usersData: IItem[] = usersData;
columns: (IColumn | string)[] = [
{
key: 'name',
_style: { width: '40%' },
_props: { color: 'danger', class: 'fw-bold' },
_colClass: 'text-center fw-bold'
},
'registered',
{ key: 'role', filter: false, sorter: false, _style: { width: '15%' }, _classes: 'text-muted small' },
{ key: 'status', _style: { width: '15%' } },
{
key: 'show',
label: '',
_style: { width: '5%' },
filter: false,
sorter: false
}
];
getBadge(status: string) {
switch (status) {
case 'Active':
return 'success';
case 'Inactive':
return 'secondary';
case 'Pending':
return 'warning';
case 'Banned':
return 'danger';
default:
return 'primary';
}
}
getItem(item: any) {
return Object.keys(item);
}
details_visible = Object.create({});
toggleDetails(item: any) {
this.details_visible[item] = !this.details_visible[item];
}
}<c-smart-table
#smartTable="cSmartTable"
[columnFilter]="true"
[columnSorter]="true"
[columns]="columns"
[itemsPerPageSelect]="true"
[itemsPerPage]="5"
[items]="usersData"
[sorterValue]="{ column: 'name', state: 'asc' }"
[tableBodyProps]="{ align: 'middle' }"
[tableFilter]="true"
[tableFootProps]="{ color: 'warning' }"
[tableHeadProps]="{ color: 'warning' }"
[tableProps]="{ hover: true, striped: true, responsive: true }"
activePage="2"
cleaner
clickableRows
footer
header
pagination
selectable
>
<ng-template cTemplateId="tableCustomHeader">
<div [cCol]="'auto'" class="p-0 ms-auto">
<button (click)="smartTable.footer = !smartTable.footer" cButton color="success">
Footer is {{ smartTable.footer ? 'ON' : 'OFF' }}
</button>
</div>
</ng-template>
<ng-template cTemplateId="tableDetails" let-item="item">
<div [visible]="this.details_visible[item.id] === true" cCollapse>
<c-card class="rounded-0">
<c-card-body>
<h5>{{ item['name'] }}</h5>
<p class="text-muted">User since: {{ item['registered'] }}</p>
</c-card-body>
<c-card-footer>
<button cButton color="info" size="sm">User Settings</button>
<button cButton class="ms-1" color="danger" size="sm">Delete</button>
</c-card-footer>
</c-card>
</div>
</ng-template>
<ng-template
cTemplateId="tableData"
let-column="column"
let-columnName="columnName"
let-item="item"
let-tdContent="tdContent"
>
<td
[cAlign]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['align'])"
[cTableActive]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['active'])"
[cTableColor]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['color'])"
[ngClass]="smartTable.getTableDataCellClass(column, item)"
>
@switch (columnName) {
@case ('status') {
<c-badge [color]="getBadge(tdContent)">
{{ item[columnName] }}
</c-badge>
}
@case ('show') {
<button (click)="toggleDetails(item.id)" cButton color="primary" size="sm" variant="outline">Show</button>
}
@default {
{{ tdContent }}
}
}
</td>
</ng-template>
</c-smart-table>import { IItem } from '@coreui/angular';
const usersData: IItem[] = [
{id: 0, name: 'John Doe', registered: '2022/01/01', role: 'Guest', status: 'Pending' },
{id: 1, name: 'Samppa Nori', registered: '2022/01/31', role: 'Member', status: 'Active', _props: { color: 'success', align: 'middle' },},
{id: 2, name: 'Estavan Lykos', registered: '2022/02/01', role: 'Staff', status: 'Banned', _cellProps: { 'name': { color: 'info', active: true }}},
{id: 3, name: 'Chetan Mohamed', registered: '2022/02/01', role: 'Admin', status: 'Inactive', _cellProps: { _all: { color: 'danger'}, role: { active: true }}},
{id: 4, name: 'Derick Maximinus', registered: '2022/03/01', role: 'Member', status: 'Pending', _selected: true },
{id: 5, name: 'Friderik Dávid', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 6, name: 'Yiorgos Avraamu', registered: '2022/01/07', role: 'Member', status: 'Active'},
{id: 7, name: 'Avram Tarasios', registered: '2022/02/08', role: 'Staff', status: 'Banned'},
{id: 8, name: 'Quintin Ed', registered: '2022/02/01', role: 'Admin', status: 'Inactive'},
{id: 9, name: 'Enéas Kwadwo', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 10, name: 'Agapetus Tadeáš', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 11, name: 'Carwyn Fachtna', registered: '2022/01/01', role: 'Member', status: 'Active'},
{id: 12, name: 'Nehemiah Tatius', registered: '2022/02/11', role: 'Staff', status: 'Banned'},
{id: 13, name: 'Ebbe Gemariah', registered: '2022/02/08', role: 'Admin', status: 'Inactive'},
{id: 14, name: 'Eustorgios Amulius', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 15, name: 'Leopold Gáspár', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 16, name: 'Pompeius René', registered: '2022/01/10', role: 'Member', status: 'Active'},
{id: 17, name: 'Paĉjo Jadon', registered: '2022/02/01', role: 'Staff', status: 'Banned'},
{id: 18, name: 'Micheal Mercurius', registered: '2022/02/11', role: 'Admin', status: 'Inactive'},
{id: 19, name: 'Ganesha Dubhghall', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 20, name: 'Hiroto Šimun', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 21, name: 'Vishnu Serghei', registered: '2022/01/01', role: 'Member', status: 'Active'},
{id: 22, name: 'Zbyněk Phoibos', registered: '2022/02/14', role: 'Staff', status: 'Banned'},
{id: 23, name: 'Aulus Agmundr', registered: '2022/01/01', role: 'Member', status: 'Pending'},
{id: 42, name: 'Ford Prefect', registered: '2001/05/25', role: 'Alien', status: 'Don\'t panic!', _cellProps: { role: { active: true }}}
]
export default usersDataDefault header
- labels generation based on column names
import { Component } from '@angular/core';
import { SmartTableComponent } from '@coreui/angular';
import usersData from './data';
@Component({
selector: 'docs-smart-table-default-header',
templateUrl: './smart-table-default-header.component.html',
imports: [SmartTableComponent]
})
export class SmartTableDefaultHeaderComponent {
protected usersData = usersData;
}<c-smart-table
[columnFilter]="true"
[columnSorter]="true"
[itemsPerPage]="10"
[items]="usersData"
[tableProps]="{ hover: true, striped: true, responsive: true }"
activePage="2"
header
pagination
/>
import { IItem } from '@coreui/angular';
const usersData: IItem[] = [
{id: 0, name: 'John Doe', registered: '2022/01/01', role: 'Guest', status: 'Pending' },
{id: 1, name: 'Samppa Nori', registered: '2022/01/31', role: 'Member', status: 'Active', _props: { color: 'success', align: 'middle' },},
{id: 2, name: 'Estavan Lykos', registered: '2022/02/01', role: 'Staff', status: 'Banned', _cellProps: { 'name': { color: 'info', active: true }}},
{id: 3, name: 'Chetan Mohamed', registered: '2022/02/01', role: 'Admin', status: 'Inactive', _cellProps: { _all: { color: 'danger'}, role: { active: true }}},
{id: 4, name: 'Derick Maximinus', registered: '2022/03/01', role: 'Member', status: 'Pending', _selected: true },
{id: 5, name: 'Friderik Dávid', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 6, name: 'Yiorgos Avraamu', registered: '2022/01/07', role: 'Member', status: 'Active'},
{id: 7, name: 'Avram Tarasios', registered: '2022/02/08', role: 'Staff', status: 'Banned'},
{id: 8, name: 'Quintin Ed', registered: '2022/02/01', role: 'Admin', status: 'Inactive'},
{id: 9, name: 'Enéas Kwadwo', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 10, name: 'Agapetus Tadeáš', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 11, name: 'Carwyn Fachtna', registered: '2022/01/01', role: 'Member', status: 'Active'},
{id: 12, name: 'Nehemiah Tatius', registered: '2022/02/11', role: 'Staff', status: 'Banned'},
{id: 13, name: 'Ebbe Gemariah', registered: '2022/02/08', role: 'Admin', status: 'Inactive'},
{id: 14, name: 'Eustorgios Amulius', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 15, name: 'Leopold Gáspár', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 16, name: 'Pompeius René', registered: '2022/01/10', role: 'Member', status: 'Active'},
{id: 17, name: 'Paĉjo Jadon', registered: '2022/02/01', role: 'Staff', status: 'Banned'},
{id: 18, name: 'Micheal Mercurius', registered: '2022/02/11', role: 'Admin', status: 'Inactive'},
{id: 19, name: 'Ganesha Dubhghall', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 20, name: 'Hiroto Šimun', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 21, name: 'Vishnu Serghei', registered: '2022/01/01', role: 'Member', status: 'Active'},
{id: 22, name: 'Zbyněk Phoibos', registered: '2022/02/14', role: 'Staff', status: 'Banned'},
{id: 23, name: 'Aulus Agmundr', registered: '2022/01/01', role: 'Member', status: 'Pending'},
{id: 42, name: 'Ford Prefect', registered: '2001/05/25', role: 'Alien', status: 'Don\'t panic!', _cellProps: { role: { active: true }}}
]
export default usersDataCustom headers
- custom table header templates ~4.7.7
To add custom column header template with a labelTemplateName:
- HTML: add
ng-templatewithcTemplateId="columnLabel_labelTemplateName"andlet-columntemplate variable - TypeScript: define
IColumns[]config with_labelTemplateIdand optional_data
import { NgTemplateOutlet } from '@angular/common';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { IColumn, IItem, SmartTableComponent, TemplateIdDirective, TooltipDirective } from '@coreui/angular';
import { cilBadge, cilCalendar, cilGroup, cilUser } from '@coreui/icons';
import { IconDirective } from '@coreui/icons-angular';
import usersData from './data';
interface IData extends IItem {
id?: number;
name?: string;
role?: string;
status?: string;
registered?: string;
_data?: { tooltip: string; icon: string };
}
@Component({
selector: 'docs-smart-table-custom-headers',
templateUrl: './smart-table-custom-headers.component.html',
standalone: true,
changeDetection: ChangeDetectionStrategy.Eager,
imports: [SmartTableComponent, TemplateIdDirective, NgTemplateOutlet, TooltipDirective, IconDirective]
})
export class SmartTableCustomHeadersComponent {
icons = { cilBadge, cilCalendar, cilGroup, cilUser };
newData: IData[] = usersData.map((item) => {
const { id, name, role, status, registered } = { ...item } as Partial<NonNullable<typeof item>>;
return { id, name, role, status, registered } as IData;
});
columns: IColumn[] = [
{
key: 'name',
label: 'Name',
_style: { width: '30%' },
_labelTemplateId: 'all',
_data: { tooltip: 'User Name', icon: 'cilUser' }
},
{
key: 'role',
label: 'Role',
_style: { width: '20%' },
_labelTemplateId: 'all',
_data: { tooltip: 'User Role', icon: 'cilGroup' }
},
{
key: 'status',
label: 'Status',
_style: { width: '25%' },
_labelTemplateId: 'all',
_data: { tooltip: 'User Status', icon: 'cilBadge' }
},
{
key: 'registered',
label: 'Registered',
_style: { width: '25%' },
_data: { tooltip: 'Date Registered', icon: 'cilCalendar' }
}
];
}<c-smart-table
[columnFilter]="true"
[columnSorter]="true"
[columns]="columns"
[itemsPerPageSelect]="true"
[items]="newData"
[tableProps]="{ hover: true, striped: true, responsive: true }"
header
itemsPerPage="10"
pagination
>
<ng-template cTemplateId="columnLabel_all" let-column>
<div class="d-inline">
{{ column.label }}
<ng-container *ngTemplateOutlet="icon; context: { $implicit: column }"></ng-container>
</div>
</ng-template>
<ng-template cTemplateId="columnLabel_registered" let-column>
<ng-container *ngTemplateOutlet="icon; context: { $implicit: column }"></ng-container>
</ng-template>
</c-smart-table>
<ng-template #icon let-column>
@if (column?._data?.tooltip) {
<div
class="d-inline"
[cTooltipTrigger]="'hover'"
[cTooltip]="$safeNavigationMigration(column?._data?.tooltip)"
cTooltipPlacement="top"
>
<svg [cIcon]="icons[column?._data?.icon]" size="sm" title="Info Icon" class="ms-1"></svg>
</div>
}
</ng-template>Column groups
The Angular Smart Table component allows grouping related columns under a shared header. This feature is useful for presenting data categorized into groups or comparing different sets. The header group spans the width of the included columns and enhances organization and readability by grouping related data. Column groups can be nested and styled.
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { IColumn, SmartTableComponent } from '@coreui/angular';
import usersData from './data';
@Component({
selector: 'docs-smart-table-column-groups',
templateUrl: './smart-table-column-groups.component.html',
standalone: true,
changeDetection: ChangeDetectionStrategy.Eager,
imports: [SmartTableComponent]
})
export class SmartTableColumnGroupsComponent {
usersData = usersData;
readonly columns: IColumn[] = [
{
group: 'main_group',
label: 'Main Group',
_style: { backgroundColor: 'var(--cui-secondary)' },
_props: { color: 'secondary', class: 'text-center text-light' },
children: [
{
group: 'subgroup_1',
label: 'Subgroup 1',
_style: { backgroundColor: 'var(--cui-light)' },
_props: { class: 'text-center text-dark' },
children: [
{
key: 'name',
label: 'Long and overflowing header label caption',
_style: { minWidth: '5rem', maxWidth: '16rem', width: '16rem' },
_props: { class: 'text-truncate' }
},
{
key: 'registered',
_style: { width: '5rem', maxWidth: '10rem', minWidth: '10rem' },
_props: { class: 'text-truncate' }
}
]
},
{
group: 'subgroup_2',
label: 'Subgroup 2',
_style: {
backgroundColor: 'var(--cui-secondary-bg)',
width: '35%'
},
_props: {
class: 'text-center'
},
children: [
{
key: 'role'
},
{
key: 'status'
}
]
}
]
}
];
}<c-smart-table
[columnFilter]="true"
[columnSorter]="true"
[columns]="columns"
[itemsPerPage]="10"
[items]="usersData"
[tableHeadProps]="{ color: 'info'}"
[tableProps]="{ hover: true, striped: true, responsive: true, bordered: true }"
activePage="2"
header
pagination
/>
Custom filters
To filter a column with a columnName :
- HTML: add
ng-templatewithcTemplateId="columnFilter_columnName"with your component - TypeScript: create
filterFunctionand pass it to thecolumnFilterValueprop ofc-smart-tablecomponent
Custom filter with MultiSelect
import { Component, ChangeDetectionStrategy } from '@angular/core';
import usersData from './data';
import {
IColumn,
IItem,
MultiSelectComponent,
MultiSelectOptionComponent,
SmartTableComponent,
TemplateIdDirective
} from '@coreui/angular';
interface IData extends IItem {
id?: number;
name?: string;
role?: string;
status?: string;
}
@Component({
selector: 'docs-smart-table-custom-filters',
templateUrl: './smart-table-custom-filters.component.html',
standalone: true,
changeDetection: ChangeDetectionStrategy.Eager,
imports: [SmartTableComponent, TemplateIdDirective, MultiSelectComponent, MultiSelectOptionComponent]
})
export class SmartTableCustomFiltersComponent {
newData: IData[] = usersData.map((item) => {
const { id, name, role, status } = { ...item } as Partial<NonNullable<typeof item>>;
return { id, name, role, status } as IData;
});
roles: string[] = [...new Set(usersData.map((item) => item['role']))];
selected: string[] = ['Staff', 'Admin'];
columns: IColumn[] = [
{
key: 'name',
_style: { width: '50%' }
},
{
key: 'role',
_style: { width: '50%' },
_props: { color: 'info', class: 'fw-bold' }
}
];
set columnFilterValue(value) {
this._columnFilterValue = { ...value };
if (!Object.entries(value).length) {
this.selected = [];
}
}
get columnFilterValue() {
return this._columnFilterValue;
}
private _columnFilterValue: any = {};
handleValueChange($event: any) {
const columnFilterValue = { ...this.columnFilterValue };
if ($event?.length) {
const selected = [...$event];
this.selected = selected;
const filterFunction = (item: any) => selected.includes(item);
this.columnFilterValue = { ...columnFilterValue, role: filterFunction };
return;
}
delete columnFilterValue.role;
this.columnFilterValue = { ...columnFilterValue };
}
}<c-smart-table
[(columnFilterValue)]="columnFilterValue"
[columnFilter]="true"
[columnSorter]="true"
[columns]="columns"
[itemsPerPageSelect]="true"
[items]="newData"
[tableProps]="{ hover: true, striped: true, responsive: true }"
header
itemsPerPage="10"
pagination
>
<ng-template cTemplateId="columnFilter_role">
<c-multi-select (valueChange)="handleValueChange($event)" [value]="selected" multiple size="sm">
@for (role of roles; track role) {
<c-multi-select-option [value]="role">{{ role }}</c-multi-select-option>
}
</c-multi-select>
</ng-template>
</c-smart-table>
Custom filter with Date Range Picker
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { endOfDay, startOfDay } from 'date-fns';
import { DateRangePickerComponent, IColumn, IItem, SmartTableComponent, TemplateIdDirective } from '@coreui/angular';
import usersData from './data';
interface IData extends IItem {
id?: number;
name?: string;
registered?: string;
}
@Component({
selector: 'docs-smart-table-custom-filters-2',
templateUrl: './smart-table-custom-filters-2.component.html',
standalone: true,
changeDetection: ChangeDetectionStrategy.Eager,
imports: [SmartTableComponent, TemplateIdDirective, DateRangePickerComponent]
})
export class SmartTableCustomFilters2Component {
newData: IData[] = usersData.map((item) => {
const { id, name, registered } = { ...item } as Partial<NonNullable<typeof item>>;
return { id, name, registered } as IData;
});
columns: IColumn[] = [
{
key: 'name',
_style: { width: '50%' }
},
{
key: 'registered',
_style: { width: '50%' },
_props: { color: 'info', class: 'fw-bold' }
}
];
calendarDate: Date = new Date(2022, 0, 1);
set startDate(value) {
this._startDate = value;
if (this._endDate) {
this.handleDateRangeChange();
}
}
get startDate() {
return this._startDate;
}
private _startDate: Date | null = new Date(2022, 0, 1);
set endDate(value) {
this._endDate = value;
this.handleDateRangeChange();
}
get endDate() {
return this._endDate;
}
private _endDate: Date | null = new Date(2022, 0, 10);
set columnFilterValue(value) {
this._columnFilterValue = { ...value };
// if (!Object.entries(value).length) {
// this.startDate = null;
// }
}
get columnFilterValue() {
return this._columnFilterValue;
}
private _columnFilterValue: any = {};
handleDateRangeChange() {
const columnFilterValue = { ...this.columnFilterValue };
if (this._startDate && this._endDate) {
const fromDate = startOfDay(this._startDate);
const toDate = endOfDay(this._endDate);
const filterFunction = (item: any) => {
const date = new Date(item);
return date >= fromDate && date <= toDate;
};
this.columnFilterValue = { ...columnFilterValue, registered: filterFunction };
return;
}
delete columnFilterValue.registered;
this.columnFilterValue = { ...columnFilterValue };
}
}<c-smart-table
[(columnFilterValue)]="columnFilterValue"
[columnFilter]="true"
[columnSorter]="true"
[columns]="columns"
[itemsPerPageSelect]="true"
[items]="newData"
[sorterValue]="{ column: 'registered', state: 'asc' }"
[tableProps]="{ hover: true, striped: true, responsive: true }"
header
itemsPerPage="10"
pagination
>
<ng-template cTemplateId="columnFilter_registered">
<c-date-range-picker
[(calendarDate)]="calendarDate"
[(endDate)]="endDate"
[(startDate)]="startDate"
[popperOptions]="{strategy: 'fixed'}"
calendars="2"
closeOnSelect
locale="en-CA"
size="sm"
/>
</ng-template>
</c-smart-table>Custom functions
Custom filter/sorter functions with nested data column
import { NgClass } from '@angular/common';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import usersData from './data-nested';
import {
AlignDirective,
SmartTableComponent,
TableActiveDirective,
TableColorDirective,
TemplateIdDirective
} from '@coreui/angular';
interface IAddress {
country?: string;
city?: string;
}
interface IUser {
id: number;
name: string;
address: IAddress;
city: IAddress['city'];
}
@Component({
selector: 'docs-smart-table-custom-functions',
templateUrl: './smart-table-custom-functions.component.html',
standalone: true,
changeDetection: ChangeDetectionStrategy.Eager,
imports: [
SmartTableComponent,
TemplateIdDirective,
TableActiveDirective,
TableColorDirective,
AlignDirective,
NgClass
]
})
export class SmartTableCustomFunctionsComponent {
usersData: IUser[] = usersData.map((item) => {
return { ...item, city: item.address.city };
});
filteredUsersData = this.usersData;
columns = [
{
key: 'name',
_style: { width: '40%' }
},
{
key: 'address',
_props: { color: 'info', class: 'fw-bold' },
filter: (item: IUser, value: string) =>
item.address?.country?.toLowerCase().startsWith(value.toLowerCase().trim()),
sorter: (itemA: IUser, itemB: IUser) => {
const a =
(itemA.address?.country?.toLowerCase().trim() ?? '') + (itemA.address?.city?.toLowerCase().trim() ?? '');
const b =
(itemB.address?.country?.toLowerCase().trim() ?? '') + (itemB.address?.city?.toLowerCase().trim() ?? '');
return a > b ? 1 : b > a ? -1 : 0;
}
},
{
key: 'city'
}
];
}<c-smart-table
#smartTable="cSmartTable"
[columnFilter]="true"
[columnSorter]="true"
[columns]="columns"
[itemsPerPageSelect]="true"
[items]="filteredUsersData"
[tableProps]="{ hover: true, striped: true, responsive: true }"
header
itemsPerPage="10"
pagination
>
<ng-template
cTemplateId="tableData"
let-column="column"
let-columnName="columnName"
let-item="item"
let-tdContent="tdContent"
>
<td
[cAlign]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['align'])"
[cTableActive]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['active'])"
[cTableColor]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['color'])"
[ngClass]="smartTable.getTableDataCellClass(column, item)"
>
@switch (columnName) {
@case ('address') {
{{ item[columnName]['country'] ?? '-' }}
}
@default {
{{ tdContent }}
}
}
</td>
</ng-template>
</c-smart-table>const usersData = [
{id: 0, address: { country: 'France', city: 'Paris'}, name: 'John Doe', registered: '2022/01/01', role: 'Guest', status: 'Pending' },
{id: 1, address: { country: 'Chile', city: 'Santiago'}, name: 'Samppa Nori', registered: '2022/01/31', role: 'Member', status: 'Active', _props: { color: 'success', align: 'middle' },},
{id: 2, address: { country: 'Germany', city: 'Berlin'}, name: 'Estavan Lykos', registered: '2022/02/01', role: 'Staff', status: 'Banned', _cellProps: { 'name': { color: 'info', active: true }}},
{id: 3, address: { country: 'Germany', city: 'Berlin'}, name: 'Chetan Mohamed', registered: '2022/02/01', role: 'Admin', status: 'Inactive', _cellProps: { _all: { color: 'danger'}, role: { active: true }}},
{id: 4, address: { country: 'Chile', city: 'Santiago'}, name: 'Derick Maximinus', registered: '2022/03/01', role: 'Member', status: 'Pending', _selected: true },
{id: 5, address: { country: 'France', city: 'Paris'}, name: 'Friderik Dávid', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 6, address: { country: 'Chile', city: 'Concepción'}, name: 'Yiorgos Avraamu', registered: '2022/01/07', role: 'Member', status: 'Active'},
{id: 7, address: { country: 'Chile', city: 'Santiago'}, name: 'Avram Tarasios', registered: '2022/02/08', role: 'Staff', status: 'Banned'},
{id: 8, address: { country: 'France', city: 'Lyon'}, name: 'Quintin Ed', registered: '2022/02/01', role: 'Admin', status: 'Inactive'},
{id: 9, address: { country: 'Chile', city: 'Santiago'}, name: 'Enéas Kwadwo', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 10, address: {}, name: 'Agapetus Tadeáš', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 11, address: { country: 'France', city: 'Paris'}, name: 'Carwyn Fachtna', registered: '2022/01/01', role: 'Member', status: 'Active'},
{id: 12, address: { country: 'Austria', city: 'Wien'}, name: 'Nehemiah Tatius', registered: '2022/02/11', role: 'Staff', status: 'Banned'},
{id: 13, address: { country: 'Austria', city: 'Wien'}, name: 'Ebbe Gemariah', registered: '2022/02/08', role: 'Admin', status: 'Inactive'},
{id: 14, address: { country: 'Austria', city: 'Salzburg'}, name: 'Eustorgios Amulius', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 15, address: { country: 'Austria', city: 'Wien'}, name: 'Leopold Gáspár', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 16, address: { country: 'Chile', city: 'Valparaíso'}, name: 'Pompeius René', registered: '2022/01/10', role: 'Member', status: 'Active'},
{id: 17, address: { country: 'Chile', city: 'Santiago'}, name: 'Paĉjo Jadon', registered: '2022/02/01', role: 'Staff', status: 'Banned'},
{id: 18, address: { country: 'Australia', city: 'Sydney'}, name: 'Micheal Mercurius', registered: '2022/02/11', role: 'Admin', status: 'Inactive'},
{id: 19, address: { country: 'Australia', city: 'Perth'}, name: 'Ganesha Dubhghall', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 20, address: { country: 'Chile', city: 'Santiago'}, name: 'Hiroto Šimun', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 21, address: { country: 'China', city: 'Shanghai'}, name: 'Vishnu Serghei', registered: '2022/01/01', role: 'Member', status: 'Active'},
{id: 22, address: { country: 'China', city: 'Shanghai'}, name: 'Zbyněk Phoibos', registered: '2022/02/14', role: 'Staff', status: 'Banned'},
{id: 23, address: { country: 'China', city: 'Shenzen'}, name: 'Aulus Agmundr', registered: '2022/01/01', role: 'Member', status: 'Pending'},
{id: 42, address: { country: 'China', city: 'Shanghai'}, name: 'Ford Prefect', registered: '2001/05/25', role: 'Alien', status: 'Don\'t panic!', _cellProps: { role: { active: true }}}
]
export default usersDataCustom filter/sorter functions with tableData template aggregated columns
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { AsyncPipe, NgClass } from '@angular/common';
import {
AlignDirective,
IColumn,
IItem,
SmartTableComponent,
TableActiveDirective,
TableColorDirective,
TemplateIdDirective
} from '@coreui/angular';
import { IUser, UserService } from './user.service';
import { delay, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Component({
selector: 'docs-smart-table-custom-functions-2',
templateUrl: './smart-table-custom-functions-2.component.html',
styles: ['.no-wrap {white-space: nowrap}'],
providers: [UserService],
imports: [
AlignDirective,
AsyncPipe,
NgClass,
SmartTableComponent,
TemplateIdDirective,
TableActiveDirective,
TableColorDirective
],
changeDetection: ChangeDetectionStrategy.Eager
})
export class SmartTableCustomFunctions2Component {
title = 'CoreUI-Angular Smart Table Example';
readonly columns: (string | IColumn)[] = [
{
key: 'user',
label: 'User',
_style: { minWidth: '10rem', maxWidth: '15rem', width: '10rem' },
_props: { class: 'text-truncate' },
filter: (item: IItem, value: string) => {
return (
item['first_name']?.toLowerCase().startsWith(value) ||
item['last_name']?.toLowerCase().startsWith(value) ||
item['age'] === parseInt(value)
);
},
sorter: (itemA, itemB): number => {
const a = parseInt(itemA['age'] ?? 0);
const b = parseInt(itemB['age'] ?? 0);
return a > b ? 1 : b > a ? -1 : 0;
}
},
{
key: 'country',
_style: { width: '5rem', maxWidth: '10rem', minWidth: '10rem' },
_props: { class: 'text-truncate' }
},
{
key: 'email',
filter: false,
sorter: false
}
];
activePage = 1;
itemsPerPage = 5;
loadingData = signal(true);
private userService = inject(UserService);
users$: Observable<IUser[]> = this.userService.getUsers().pipe(
delay(1000),
tap(() => {
this.loadingData.set(false);
})
);
}<div class="m-3">
<hr />
<c-smart-table
#smartTable="cSmartTable"
[(activePage)]="activePage"
[columnFilter]="true"
[columnSorter]="true"
[columns]="columns"
[itemsPerPage]="itemsPerPage"
[items]="users$ | async"
[loading]="loadingData()"
[tableProps]="{ hover: true, striped: true, responsive: true }"
header
pagination
>
<ng-template
cTemplateId="tableData"
let-column="column"
let-columnName="columnName"
let-item="item"
let-tdContent="tdContent"
>
<td
[cAlign]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['align'])"
[cTableActive]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['active'])"
[cTableColor]="$safeNavigationMigration(smartTable.getTableDataCellProps(column, item)?.['color'])"
[ngClass]="smartTable.getTableDataCellClass(column, item)"
>
@switch (columnName) {
@case ('user') {
{{ item['first_name'] }}
{{ item['last_name'] }}
<br />
age: {{ item['age'] }}
}
@default {
{{ tdContent }}
}
}
</td>
</ng-template>
</c-smart-table>
<hr />
</div>import { Injectable } from '@angular/core';
import { of } from 'rxjs';
export interface IUser {
id: string;
first_name: string;
last_name: string;
email: string;
country: string;
ip_address?: string;
registered?: string;
my_customer?: string;
test?: boolean;
age: number;
}
@Injectable()
export class UserService {
getUsers() {
return of(this.users);
}
users: IUser[] = [
{
id: '1',
first_name: 'Rowland',
last_name: 'Jumont',
email: '[email protected]',
country: 'China',
ip_address: '248.90.215.202',
registered: '2019-04-27',
age: 35
},
{
id: '2',
first_name: 'Melloney',
last_name: 'Swindon',
email: '[email protected]',
country: 'Tunisia',
ip_address: '64.28.239.34',
registered: '2017-09-09',
age: 73
},
{
id: '3',
first_name: 'Martica',
last_name: 'Farfolomeev',
email: '[email protected]',
country: 'Philippines',
ip_address: '135.56.179.89',
registered: '2021-02-26',
age: 26
},
{
id: '4',
first_name: 'Modestia',
last_name: 'Batten',
email: '[email protected]',
country: 'Philippines',
ip_address: '119.63.79.34',
registered: '2022-03-24',
age: 46
},
{
id: '5',
first_name: 'Peyter',
last_name: 'Andrusov',
email: '[email protected]',
country: 'Sweden',
ip_address: '127.179.144.121',
registered: '2017-09-15',
age: 76
},
{
id: '6',
first_name: 'Brandise',
last_name: 'Lygoe',
email: '[email protected]',
country: 'Norway',
ip_address: '7.28.40.151',
registered: '2022-04-08',
age: 42
},
{
id: '7',
first_name: 'Zitella',
last_name: 'Renshall',
email: '[email protected]',
country: 'Brazil',
ip_address: '191.192.158.32',
registered: '2020-10-23',
age: 46
},
{
id: '8',
first_name: 'Cynthy',
last_name: 'Vaan',
email: '[email protected]',
country: 'Indonesia',
ip_address: '80.142.100.40',
registered: '2017-05-08',
age: 40
},
{
id: '9',
first_name: 'Nicky',
last_name: 'Elgy',
email: '[email protected]',
country: 'China',
ip_address: '233.139.91.55',
registered: '2020-08-01',
age: 45
},
{
id: '10',
first_name: 'Portie',
last_name: 'Van der Brugge',
email: '[email protected]',
country: 'Netherlands',
ip_address: '77.3.161.172',
registered: '2020-11-25',
age: 58
},
{
id: '11',
first_name: 'Melessa',
last_name: 'Burgill',
email: '[email protected]',
country: 'China',
ip_address: '233.167.158.162',
registered: '2023-01-22',
age: 41
},
{
id: '12',
first_name: 'Roman',
last_name: 'Tomowicz',
email: '[email protected]',
country: 'United States',
ip_address: '85.13.181.7',
registered: '2021-06-23',
age: 61
},
{
id: '13',
first_name: 'Velvet',
last_name: 'Swafford',
email: '[email protected]',
country: 'Indonesia',
ip_address: '34.148.241.247',
registered: '2018-03-25',
age: 55
},
{
id: '14',
first_name: 'Benoite',
last_name: 'Langelaan',
email: '[email protected]',
country: 'Albania',
ip_address: '138.86.125.234',
registered: '2021-06-29',
age: 52
},
{
id: '15',
first_name: 'Chantalle',
last_name: 'Enriques',
email: '[email protected]',
country: 'Brazil',
ip_address: '24.210.20.220',
registered: '2017-04-18',
age: 29
},
{
id: '16',
first_name: 'Enid',
last_name: 'Dulin',
email: '[email protected]',
country: 'China',
ip_address: '209.211.189.165',
registered: '2022-07-13',
age: 72
},
{
id: '17',
first_name: 'Vasilis',
last_name: 'Shew',
email: '[email protected]',
country: 'Pakistan',
ip_address: '84.141.13.86',
registered: '2021-07-30',
age: 42
},
{
id: '18',
first_name: 'Felice',
last_name: 'Lawrence',
email: '[email protected]',
country: 'China',
ip_address: '212.53.202.73',
registered: '2019-03-11',
age: 74
},
{
id: '19',
first_name: 'Tilly',
last_name: 'Goodin',
email: '[email protected]',
country: 'France',
ip_address: '155.213.172.112',
registered: '2021-05-13',
age: 63
},
{
id: '20',
first_name: 'Linda',
last_name: 'Lent',
email: '[email protected]',
country: 'Brazil',
ip_address: '148.179.11.167',
registered: '2017-09-24',
age: 56
},
{
id: '21',
first_name: 'Laina',
last_name: 'Carbry',
email: '[email protected]',
country: 'China',
ip_address: '193.84.239.208',
registered: '2017-05-14',
age: 66
},
{
id: '22',
first_name: 'Tremayne',
last_name: 'Wilcot',
email: '[email protected]',
country: 'Indonesia',
ip_address: '177.192.189.51',
registered: '2019-05-01',
age: 79
},
{
id: '23',
first_name: 'Lisha',
last_name: 'Casacchia',
email: '[email protected]',
country: 'Iran',
ip_address: '140.229.23.132',
registered: '2018-05-11',
age: 21
},
{
id: '24',
first_name: 'Christean',
last_name: 'Donaghy',
email: '[email protected]',
country: 'Indonesia',
ip_address: '194.150.3.107',
registered: '2017-11-09',
age: 69
},
{
id: '25',
first_name: 'Rabbi',
last_name: 'Commings',
email: '[email protected]',
country: 'China',
ip_address: '170.102.59.120',
registered: '2017-09-14',
age: 76
},
{
id: '26',
first_name: 'Lazar',
last_name: 'Brightman',
email: '[email protected]',
country: 'China',
ip_address: '96.169.177.183',
registered: '2019-03-12',
age: 61
},
{
id: '27',
first_name: 'Fara',
last_name: 'Pixton',
email: '[email protected]',
country: 'China',
ip_address: '125.192.244.33',
registered: '2020-07-02',
age: 60
},
{
id: '28',
first_name: 'Frankie',
last_name: 'Symmers',
email: '[email protected]',
country: 'United Kingdom',
ip_address: '15.95.185.50',
registered: '2017-05-06',
age: 60
},
{
id: '29',
first_name: 'Sullivan',
last_name: 'Duchant',
email: '[email protected]',
country: 'Thailand',
ip_address: '52.69.63.4',
registered: '2020-09-21',
age: 29
},
{
id: '30',
first_name: 'Gale',
last_name: 'Yggo',
email: '[email protected]',
country: 'Luxembourg',
ip_address: '10.147.136.158',
registered: '2017-03-01',
age: 27
},
{
id: '31',
first_name: 'Daphne',
last_name: 'Moscone',
email: '[email protected]',
country: 'Poland',
ip_address: '240.179.82.48',
registered: '2019-01-25',
age: 27
},
{
id: '32',
first_name: 'Ainslie',
last_name: 'Piperley',
email: '[email protected]',
country: 'China',
ip_address: '212.140.73.147',
registered: '2018-08-16',
age: 36
},
{
id: '33',
first_name: 'Esme',
last_name: 'Trousdell',
email: '[email protected]',
country: 'Mexico',
ip_address: '120.60.206.157',
registered: '2022-01-25',
age: 57
},
{
id: '34',
first_name: 'Aurelia',
last_name: 'Salway',
email: '[email protected]',
country: 'China',
ip_address: '126.162.115.255',
registered: '2020-11-11',
age: 78
},
{
id: '35',
first_name: 'Terry',
last_name: 'McKern',
email: '[email protected]',
country: 'United States',
ip_address: '112.238.5.241',
registered: '2017-08-22',
age: 28
},
{
id: '36',
first_name: 'Alphonse',
last_name: 'Osgodby',
email: '[email protected]',
country: 'France',
ip_address: '190.137.124.53',
registered: '2019-10-20',
age: 25
},
{
id: '37',
first_name: 'Boonie',
last_name: 'Gytesham',
email: '[email protected]',
country: 'Germany',
ip_address: '139.137.15.193',
registered: '2017-04-07',
age: 64
},
{
id: '38',
first_name: 'Robinette',
last_name: 'Denisyuk',
email: '[email protected]',
country: 'Guatemala',
ip_address: '39.59.210.232',
registered: '2021-12-24',
age: 55
},
{
id: '39',
first_name: 'Kerby',
last_name: 'Walden',
email: '[email protected]',
country: 'China',
ip_address: '52.147.135.77',
registered: '2019-09-04',
age: 24
},
{
id: '40',
first_name: 'Goldie',
last_name: 'MacMoyer',
email: '[email protected]',
country: 'Greece',
ip_address: '200.8.237.147',
registered: '2019-10-21',
age: 64
},
{
id: '41',
first_name: 'Clemence',
last_name: 'Tyrie',
email: '[email protected]',
country: 'Sweden',
ip_address: '180.56.118.209',
registered: '2019-06-30',
age: 77
},
{
id: '42',
first_name: 'Stormy',
last_name: 'Grog',
email: '[email protected]',
country: 'Japan',
ip_address: '80.0.4.237',
registered: '2017-10-11',
age: 79
},
{
id: '43',
first_name: 'Loutitia',
last_name: 'Andreev',
email: '[email protected]',
country: 'Japan',
ip_address: '105.113.159.240',
registered: '2022-11-19',
age: 61
},
{
id: '44',
first_name: 'Ashla',
last_name: 'Farrer',
email: '[email protected]',
country: 'Philippines',
ip_address: '108.41.116.114',
registered: '2017-06-30',
age: 75
},
{
id: '45',
first_name: 'Gaye',
last_name: 'Gwilym',
email: '[email protected]',
country: 'Peru',
ip_address: '108.147.41.3',
registered: '2017-11-02',
age: 56
},
{
id: '46',
first_name: 'Harley',
last_name: 'Vecard',
email: '[email protected]',
country: 'Russia',
ip_address: '166.123.164.35',
registered: '2021-11-08',
age: 74
},
{
id: '47',
first_name: 'Chadwick',
last_name: 'Francke',
email: '[email protected]',
country: 'Canada',
ip_address: '106.228.188.3',
registered: '2019-05-16',
age: 36
},
{
id: '48',
first_name: 'Chrisse',
last_name: 'Watkin',
email: '[email protected]',
country: 'Poland',
ip_address: '104.5.101.18',
registered: '2020-03-23',
age: 36
},
{
id: '49',
first_name: 'Denyse',
last_name: 'Freeman',
email: '[email protected]',
country: 'China',
ip_address: '57.23.133.66',
registered: '2021-03-23',
age: 32
},
{
id: '50',
first_name: 'Justine',
last_name: 'Conibere',
email: '[email protected]',
country: 'Jamaica',
ip_address: '98.77.41.3',
registered: '2021-10-05',
age: 68
},
{
id: '51',
first_name: 'Kalila',
last_name: 'Mongenot',
email: '[email protected]',
country: 'Panama',
ip_address: '110.15.203.86',
registered: '2020-07-31',
age: 48
},
{
id: '52',
first_name: 'Claresta',
last_name: 'Monahan',
email: '[email protected]',
country: 'Indonesia',
ip_address: '26.246.210.121',
registered: '2021-10-01',
age: 74
},
{
id: '53',
first_name: 'Lindsey',
last_name: 'Huchot',
email: '[email protected]',
country: 'Azerbaijan',
ip_address: '167.199.9.177',
registered: '2022-05-12',
age: 51
},
{
id: '54',
first_name: 'Luciano',
last_name: 'Ollerearnshaw',
email: '[email protected]',
country: 'Peru',
ip_address: '232.123.57.3',
registered: '2022-05-18',
age: 79
},
{
id: '55',
first_name: 'Artie',
last_name: 'Focke',
email: '[email protected]',
country: 'Poland',
ip_address: '141.229.245.46',
registered: '2021-11-06',
age: 58
},
{
id: '56',
first_name: 'Francyne',
last_name: 'Gravestone',
email: '[email protected]',
country: 'Indonesia',
ip_address: '60.151.142.197',
registered: '2017-07-27',
age: 62
},
{
id: '57',
first_name: 'Kare',
last_name: 'Mayling',
email: '[email protected]',
country: 'Indonesia',
ip_address: '179.31.176.197',
registered: '2018-11-14',
age: 57
},
{
id: '58',
first_name: 'Tonya',
last_name: 'Cressor',
email: '[email protected]',
country: 'China',
ip_address: '215.203.39.111',
registered: '2018-06-16',
age: 34
},
{
id: '59',
first_name: 'Cindee',
last_name: 'Reddington',
email: '[email protected]',
country: 'South Korea',
ip_address: '63.234.142.163',
registered: '2021-07-07',
age: 78
},
{
id: '60',
first_name: 'Felice',
last_name: 'Sneezum',
email: '[email protected]',
country: 'Indonesia',
ip_address: '225.218.11.88',
registered: '2019-03-31',
age: 30
},
{
id: '61',
first_name: 'Aluin',
last_name: 'Braben',
email: '[email protected]',
country: 'Japan',
ip_address: '138.199.112.47',
registered: '2017-05-30',
age: 79
},
{
id: '62',
first_name: 'Gerome',
last_name: 'Rowlinson',
email: '[email protected]',
country: 'Ireland',
ip_address: '205.131.176.187',
registered: '2020-12-05',
age: 77
},
{
id: '63',
first_name: 'Ly',
last_name: 'Roze',
email: '[email protected]',
country: 'Czech Republic',
ip_address: '53.186.166.82',
registered: '2017-08-22',
age: 78
},
{
id: '64',
first_name: 'Fan',
last_name: 'Bente',
email: '[email protected]',
country: 'Bulgaria',
ip_address: '208.170.126.232',
registered: '2020-09-06',
age: 37
},
{
id: '65',
first_name: 'Stephanus',
last_name: 'Deverson',
email: '[email protected]',
country: 'Portugal',
ip_address: '79.162.137.0',
registered: '2019-12-23',
age: 44
},
{
id: '66',
first_name: 'Julienne',
last_name: 'Brydell',
email: '[email protected]',
country: 'Azerbaijan',
ip_address: '87.32.218.94',
registered: '2019-06-12',
age: 56
},
{
id: '67',
first_name: 'Brit',
last_name: 'Choules',
email: '[email protected]',
country: 'Portugal',
ip_address: '122.153.96.157',
registered: '2018-01-05',
age: 43
},
{
id: '68',
first_name: 'Rawley',
last_name: 'Tiebe',
email: '[email protected]',
country: 'Jamaica',
ip_address: '46.39.61.102',
registered: '2019-08-22',
age: 64
},
{
id: '69',
first_name: 'Morley',
last_name: 'Mellmer',
email: '[email protected]',
country: 'Portugal',
ip_address: '76.235.54.184',
registered: '2019-07-14',
age: 29
},
{
id: '70',
first_name: 'Reinaldos',
last_name: 'Fernandes',
email: '[email protected]',
country: 'Indonesia',
ip_address: '175.71.20.28',
registered: '2021-03-03',
age: 30
},
{
id: '71',
first_name: 'Aron',
last_name: 'Marsie',
email: '[email protected]',
country: 'Philippines',
ip_address: '163.5.128.113',
registered: '2021-12-12',
age: 29
},
{
id: '72',
first_name: 'Mignon',
last_name: 'MacLeod',
email: '[email protected]',
country: 'Peru',
ip_address: '73.76.76.203',
registered: '2018-12-28',
age: 48
},
{
id: '73',
first_name: 'Jobina',
last_name: 'Antonijevic',
email: '[email protected]',
country: 'Sweden',
ip_address: '235.102.55.226',
registered: '2021-08-17',
age: 68
},
{
id: '74',
first_name: 'Fredric',
last_name: 'Tuke',
email: '[email protected]',
country: 'Angola',
ip_address: '224.210.197.236',
registered: '2019-11-06',
age: 70
},
{
id: '75',
first_name: 'Ron',
last_name: 'Cacacie',
email: '[email protected]',
country: 'Philippines',
ip_address: '219.250.115.66',
registered: '2020-06-01',
age: 57
},
{
id: '76',
first_name: 'Natassia',
last_name: 'Whisker',
email: '[email protected]',
country: 'Brazil',
ip_address: '129.209.182.96',
registered: '2019-07-06',
age: 24
},
{
id: '77',
first_name: 'Laurena',
last_name: 'Yemm',
email: '[email protected]',
country: 'Ukraine',
ip_address: '234.36.49.108',
registered: '2019-04-26',
age: 78
},
{
id: '78',
first_name: 'Brett',
last_name: 'Bundey',
email: '[email protected]',
country: 'China',
ip_address: '147.107.165.124',
registered: '2017-03-15',
age: 25
},
{
id: '79',
first_name: 'Gwendolyn',
last_name: 'Aleevy',
email: '[email protected]',
country: 'Indonesia',
ip_address: '236.170.159.101',
registered: '2021-11-24',
age: 55
},
{
id: '80',
first_name: 'Marice',
last_name: 'Corston',
email: '[email protected]',
country: 'Guadeloupe',
ip_address: '137.95.107.239',
registered: '2022-10-06',
age: 30
},
{
id: '81',
first_name: 'Porty',
last_name: 'Pembry',
email: '[email protected]',
country: 'China',
ip_address: '124.228.125.51',
registered: '2019-12-03',
age: 52
},
{
id: '82',
first_name: 'Kenyon',
last_name: 'Banker',
email: '[email protected]',
country: 'Russia',
ip_address: '45.23.186.203',
registered: '2019-08-12',
age: 44
},
{
id: '83',
first_name: 'Friedrich',
last_name: 'Phettis',
email: '[email protected]',
country: 'Comoros',
ip_address: '123.55.170.241',
registered: '2018-12-03',
age: 42
},
{
id: '84',
first_name: 'Dennie',
last_name: 'Rickwood',
email: '[email protected]',
country: 'Vietnam',
ip_address: '195.208.216.82',
registered: '2020-05-07',
age: 46
},
{
id: '85',
first_name: 'Stafford',
last_name: 'Jendrich',
email: '[email protected]',
country: 'China',
ip_address: '127.23.207.10',
registered: '2019-11-16',
age: 53
},
{
id: '86',
first_name: 'Weston',
last_name: 'Westcarr',
email: '[email protected]',
country: 'Peru',
ip_address: '244.140.106.99',
registered: '2018-01-08',
age: 66
},
{
id: '87',
first_name: 'Karole',
last_name: 'Cumberledge',
email: '[email protected]',
country: 'Macedonia',
ip_address: '5.178.26.46',
registered: '2017-06-15',
age: 73
},
{
id: '88',
first_name: 'Fanchon',
last_name: 'Alenshev',
email: '[email protected]',
country: 'Indonesia',
ip_address: '135.228.36.1',
registered: '2018-11-17',
age: 50
},
{
id: '89',
first_name: 'Kele',
last_name: 'Beech',
email: '[email protected]',
country: 'Russia',
ip_address: '157.92.244.151',
registered: '2020-10-19',
age: 68
},
{
id: '90',
first_name: 'Crystal',
last_name: 'Westgate',
email: '[email protected]',
country: 'Greece',
ip_address: '69.12.109.157',
registered: '2019-05-04',
age: 61
},
{
id: '91',
first_name: 'Robinet',
last_name: 'Pargent',
email: '[email protected]',
country: 'Indonesia',
ip_address: '105.176.209.246',
registered: '2020-11-06',
age: 66
},
{
id: '92',
first_name: 'Ritchie',
last_name: 'Dealey',
email: '[email protected]',
country: 'Tanzania',
ip_address: '11.217.187.232',
registered: '2021-07-07',
age: 47
},
{
id: '93',
first_name: 'Karyl',
last_name: 'Bischop',
email: '[email protected]',
country: 'Thailand',
ip_address: '220.178.23.247',
registered: '2020-03-31',
age: 48
},
{
id: '94',
first_name: 'Lemmie',
last_name: 'Tatterton',
email: '[email protected]',
country: 'Indonesia',
ip_address: '252.191.234.163',
registered: '2022-04-13',
age: 27
},
{
id: '95',
first_name: 'Ladonna',
last_name: 'Deverille',
email: '[email protected]',
country: 'Dominican Republic',
ip_address: '25.248.243.242',
registered: '2018-01-27',
age: 37
},
{
id: '96',
first_name: 'Austin',
last_name: 'Girk',
email: '[email protected]',
country: 'China',
ip_address: '180.82.151.113',
registered: '2020-05-24',
age: 33
},
{
id: '97',
first_name: 'Letisha',
last_name: 'Groveham',
email: '[email protected]',
country: 'Sweden',
ip_address: '95.159.112.187',
registered: '2019-06-23',
age: 41
},
{
id: '98',
first_name: 'Leonerd',
last_name: 'Hollingby',
email: '[email protected]',
country: 'Indonesia',
ip_address: '109.27.62.206',
registered: '2018-04-13',
age: 73
},
{
id: '99',
first_name: 'Ally',
last_name: 'Chadwin',
email: '[email protected]',
country: 'Lebanon',
ip_address: '168.77.72.236',
registered: '2022-07-15',
age: 41
}
];
}Summary row
- Starting from ~4.5.26you can pass an ng-template with
cTemplateId="tableSummaryRow"as a c-smart-table content. - Style
trandthelements according to your needs. Display any data you want.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IItem, SmartTableComponent, TableColorDirective, TemplateIdDirective } from '@coreui/angular';
import usersData from './data';
@Component({
selector: 'docs-smart-table-summary-row',
templateUrl: './smart-table-summary-row.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SmartTableComponent, TemplateIdDirective, TableColorDirective]
})
export class SmartTableSummaryRowComponent {
readonly selectedItemsCount = signal(0);
protected readonly usersData = usersData.map((item) => {
const { name, registered, role, status, _props } = item;
return { name, registered, role, status, _props };
});
onSelectedItemsChange(selectedItems: IItem[]) {
this.selectedItemsCount.set(selectedItems.length ?? 0);
}
}<c-smart-table
[itemsPerPage]="5"
[items]="usersData"
[tableProps]="{ hover: true, striped: true, responsive: true }"
header
pagination
selectable
(selectedItemsChange)="onSelectedItemsChange($event)"
#table="cSmartTable"
>
<ng-template cTemplateId="tableSummaryRow">
<tr cTableColor="info">
<th style="width: 15%;">Selected: {{selectedItemsCount()}}.</th>
<th [attr.colspan]="table.columns.length">
Items count: {{usersData.length}}.
</th>
</tr>
</ng-template>
</c-smart-table>import { IItem } from '@coreui/angular';
const usersData: IItem[] = [
{id: 0, name: 'John Doe', registered: '2022/01/01', role: 'Guest', status: 'Pending' },
{id: 1, name: 'Samppa Nori', registered: '2022/01/31', role: 'Member', status: 'Active', _props: { color: 'success', align: 'middle' },},
{id: 2, name: 'Estavan Lykos', registered: '2022/02/01', role: 'Staff', status: 'Banned', _cellProps: { 'name': { color: 'info', active: true }}},
{id: 3, name: 'Chetan Mohamed', registered: '2022/02/01', role: 'Admin', status: 'Inactive', _cellProps: { _all: { color: 'danger'}, role: { active: true }}},
{id: 4, name: 'Derick Maximinus', registered: '2022/03/01', role: 'Member', status: 'Pending', _selected: true },
{id: 5, name: 'Friderik Dávid', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 6, name: 'Yiorgos Avraamu', registered: '2022/01/07', role: 'Member', status: 'Active'},
{id: 7, name: 'Avram Tarasios', registered: '2022/02/08', role: 'Staff', status: 'Banned'},
{id: 8, name: 'Quintin Ed', registered: '2022/02/01', role: 'Admin', status: 'Inactive'},
{id: 9, name: 'Enéas Kwadwo', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 10, name: 'Agapetus Tadeáš', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 11, name: 'Carwyn Fachtna', registered: '2022/01/01', role: 'Member', status: 'Active'},
{id: 12, name: 'Nehemiah Tatius', registered: '2022/02/11', role: 'Staff', status: 'Banned'},
{id: 13, name: 'Ebbe Gemariah', registered: '2022/02/08', role: 'Admin', status: 'Inactive'},
{id: 14, name: 'Eustorgios Amulius', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 15, name: 'Leopold Gáspár', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 16, name: 'Pompeius René', registered: '2022/01/10', role: 'Member', status: 'Active'},
{id: 17, name: 'Paĉjo Jadon', registered: '2022/02/01', role: 'Staff', status: 'Banned'},
{id: 18, name: 'Micheal Mercurius', registered: '2022/02/11', role: 'Admin', status: 'Inactive'},
{id: 19, name: 'Ganesha Dubhghall', registered: '2022/03/01', role: 'Member', status: 'Pending'},
{id: 20, name: 'Hiroto Šimun', registered: '2022/01/21', role: 'Staff', status: 'Active'},
{id: 21, name: 'Vishnu Serghei', registered: '2022/01/01', role: 'Member', status: 'Active'},
{id: 22, name: 'Zbyněk Phoibos', registered: '2022/02/14', role: 'Staff', status: 'Banned'},
{id: 23, name: 'Aulus Agmundr', registered: '2022/01/01', role: 'Member', status: 'Pending'},
{id: 42, name: 'Ford Prefect', registered: '2001/05/25', role: 'Alien', status: 'Don\'t panic!', _cellProps: { role: { active: true }}}
]
export default usersDataExternal data
One of the key features of CoreUI Angular Smart Table (Angular DataTable) is the ability to load data from an external source, such as an API or a server-side script. This can be useful if you have a large amount of data that you don’t want to load all at once, or if you want to allow users to interact with the data without having to reload the page.
Use the HttpClient for communication with the data source, and get the external data from backend into a CoreUI Angular Smart Table (Angular DataTable).
Here is an example of how you might use CoreUI Angular Smart Table with external data (10.000+ records):
import { AsyncPipe } from '@angular/common';
import { Component, OnDestroy, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, retry, takeUntil, tap } from 'rxjs/operators';
import {
AlertComponent,
IColumn,
IColumnFilterValue,
ISorterValue,
SmartPaginationComponent,
SmartTableComponent
} from '@coreui/angular';
import { IApiParams, IUsers, UsersService } from './users.service';
export interface IParams {
activePage?: number;
columnFilterValue?: IColumnFilterValue;
itemsPerPage?: number;
loadingData?: boolean;
sorterValue?: ISorterValue;
totalPages?: number;
}
@Component({
selector: 'docs-smart-table-external-data',
templateUrl: './smart-table-external-data.component.html',
providers: [UsersService],
standalone: true,
changeDetection: ChangeDetectionStrategy.Eager,
imports: [SmartTableComponent, SmartPaginationComponent, AlertComponent, AsyncPipe]
})
export class SmartTableExternalDataComponent implements OnInit, OnDestroy {
constructor(private usersService: UsersService) {}
title = 'CoreUI Angular Smart Table Example';
readonly columns: (string | IColumn)[] = [
{
key: 'first_name',
_style: { width: '15%' }
},
{
key: 'last_name',
_style: { width: '15%' }
},
'email',
{
key: 'country',
_style: { width: '22%' }
},
{
key: 'ip_address',
label: 'IP',
_style: { width: '15%' }
}
];
readonly activePage$ = new BehaviorSubject(0);
readonly columnFilterValue$ = new BehaviorSubject({});
readonly itemsPerPage$ = new BehaviorSubject(5);
readonly loadingData$ = new BehaviorSubject<boolean>(true);
readonly totalPages$ = new BehaviorSubject<number>(1);
readonly sorterValue$ = new BehaviorSubject({});
readonly totalItems$ = new BehaviorSubject(0);
readonly apiParams$ = new BehaviorSubject<IApiParams>({ limit: this.itemsPerPage$.value, offset: 0 });
readonly errorMessage$ = new Subject<string>();
readonly retry$ = new Subject<boolean>();
readonly props$: Observable<IParams> = combineLatest([
this.activePage$,
this.columnFilterValue$,
this.itemsPerPage$,
this.loadingData$,
this.sorterValue$,
this.totalPages$
]).pipe(
debounceTime(100),
map(([activePage, columnFilterValue, itemsPerPage, loadingData, sorterValue, totalPages]) => ({
activePage,
columnFilterValue,
itemsPerPage,
loadingData,
sorterValue,
totalPages
}))
);
usersData$!: Observable<IUsers[] | unknown>;
readonly #destroy$ = new Subject<boolean>();
private _apiParams: IApiParams = {};
set apiParams(value: any) {
const params = {
...this._apiParams,
...value
};
const entries = new Map(Object.entries(params));
entries.forEach((value, key, map) => {
if (value === '' || value === undefined || value === null) {
map.delete(key);
}
});
const apiParams = Object.fromEntries(entries);
this.loadingData$.next(true);
this._apiParams = { ...apiParams };
this.retry$.next(true);
this.apiParams$.next({ ...apiParams });
}
ngOnDestroy(): void {
this.#destroy$.next(true);
}
ngOnInit(): void {
this.activePage$.pipe(takeUntil(this.#destroy$)).subscribe((page) => {
const limit = this.itemsPerPage$.value;
const offset = limit * page - limit;
this.apiParams = { offset, limit };
});
this.itemsPerPage$.pipe(distinctUntilChanged(), takeUntil(this.#destroy$)).subscribe((limit) => {
const totalPages = Math.ceil(this.totalItems$.value / limit) ?? 1;
this.totalPages$.next(totalPages);
});
this.totalItems$.pipe(distinctUntilChanged(), takeUntil(this.#destroy$)).subscribe((totalItems) => {
const totalPages = Math.ceil(totalItems / this.itemsPerPage$.value) ?? 1;
this.totalPages$.next(totalPages);
});
this.totalPages$.pipe(takeUntil(this.#destroy$)).subscribe((totalPages) => {
const activePage = this.activePage$.value > totalPages ? totalPages : this.activePage$.value;
this.setActivePage(activePage);
});
this.usersData$ = this.usersService.getUsers(this.apiParams$).pipe(
retry({
delay: (error) => {
console.warn('Retry: ', error);
this.errorMessage$.next(error.message ?? `Error: ${JSON.stringify(error)}`);
this.loadingData$.next(false);
return this.retry$;
}
}),
tap((response) => {
this.totalItems$.next(response.number_of_matching_records);
if (response.number_of_records) {
this.errorMessage$.next('');
}
this.retry$.next(false);
this.loadingData$.next(false);
}),
map((response) => {
return response.records;
})
);
}
handleColumnFilterValueChange(columnFilterValue: IColumnFilterValue) {
this.setActivePage(1);
this.apiParams = { ...columnFilterValue };
this.columnFilterValue$.next(columnFilterValue);
}
handleSorterValueChange(sorterValue: ISorterValue) {
this.sorterValue$.next(!!sorterValue.state ? sorterValue : {});
const sort = !!sorterValue.state ? `${sorterValue.column}%${sorterValue.state}` : '';
this.apiParams = { sort };
}
handleFilteredItemsChange(filteredItems: IUsers[]) {
// console.table(filteredItems);
}
handleActivePageChange(page: number) {
this.setActivePage(page);
}
handleItemsPerPageChange(limit: number) {
this.itemsPerPage$.next(limit);
}
setActivePage(page: number) {
page = page > 0 && this.totalPages$.value + 1 > page ? page : 1;
this.activePage$.next(page);
}
}@if (props$ | async; as props) {
<c-smart-table
(columnFilterValueChange)="handleColumnFilterValueChange($event)"
(filteredItemsChange)="handleFilteredItemsChange($event)"
(itemsPerPageChange)="handleItemsPerPageChange($event)"
(sorterValueChange)="handleSorterValueChange($event)"
[columnFilterValue]="props.columnFilterValue"
[columnFilter]="{ external: true, lazy: false }"
[columnSorter]="{ external: true, resettable: true }"
[columns]="columns"
[itemsPerPageOptions]="[5,10,20]"
[itemsPerPageSelect]="true"
[itemsPerPage]="props.itemsPerPage"
[items]="(usersData$ | async) ?? []"
[loading]="props.loadingData"
[sorterValue]="props.sorterValue"
[tableProps]="{ hover: true, striped: true, responsive: true }"
cleaner
header
/>
@if (props.totalPages) {
<c-smart-pagination
[activePage]="props.activePage"
(activePageChange)="handleActivePageChange($event)"
[pages]="props.totalPages"
/>
}
}
@if (errorMessage$ | async; as errorMessage) {
<hr>
<c-alert>{{ errorMessage }}</c-alert>
}import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged, retry, switchMap } from 'rxjs/operators';
const httpOptions = {
// headers: new HttpHeaders({
// 'Content-Type': 'application/json',
// 'Access-Control-Allow-Origin': '*',
// Connection: 'keep-alive'
// })
};
export interface IData {
number_of_records: number;
number_of_matching_records: number;
records: IUsers[];
}
export interface IUsers {
id: number;
first_name: string;
last_name: string;
email: string;
country: string;
ip_address: string;
registered: string;
}
export interface IApiParams {
offset?: number;
limit?: number;
columnFilter?: string;
columnSorter?: string;
sort?: string;
}
@Injectable()
export class UsersService {
constructor(
private httpClient: HttpClient
) {
}
usersUrl = 'https://apitest.coreui.io/demos/users';
/** GET users from the server */
getUsers(config$: BehaviorSubject<IApiParams>): Observable<any> {
return config$.pipe(
debounceTime(100),
distinctUntilChanged(
(previous, current) => {
return JSON.stringify(previous) === JSON.stringify(current);
}
),
switchMap((config) => this.fetchData(config))
);
}
private fetchData(params: IApiParams): Observable<IData> {
const apiParams = {
...params
};
const httpParams: HttpParams = new HttpParams({ fromObject: apiParams });
const options = Object.keys(httpParams).length
? { params: httpParams, ...httpOptions }
: { params: {}, ...httpOptions };
return this.httpClient
.get<IData>(this.usersUrl, options)
.pipe(
retry({ count: 1, delay: 1000, resetOnSuccess: true }),
catchError(this.handleHttpError)
);
}
private handleHttpError(error: HttpErrorResponse) {
return throwError(() => error);
}
}API
SmartTable Module
import { NgModule } from '@angular/core';
import { SharedModule, SmartTableModule } from '@coreui/angular';
@NgModule({
imports: [SmartTableModule, SharedModule]
})
export class CustomAppModule {}SmartTable Standalone
import { Component } from '@angular/core';
import { SmartTableComponent } from '@coreui/angular';
@Component({
templateUrl: './custom-app.component.html',
imports: [SmartTableComponent],
standalone: true
})
export class CustomAppComponent {}c-smart-table
component
import { SmartTableComponent } from '@coreui/angular-pro'Props
Events
Templates
You can pass an ng-template with cTemplateId as a c-smart-table content.
Available templates with [cTemplateId] names:
tableCustomHeader(~4.4.5goes to the header row with table filter/cleaner)tableData(goes totdhtml element for specifiedcolumnName)tableDetails(goes to separate row as row details, for everytr)tableSummaryRow(~4.5.26goes to the table footer)columnFilter_*(see: Custom Filters)columnLabel_*(~4.7.7see: Custom Headers)
All templates are optional.
To use custom templates you’ll have to:
import {SharedModule} from '@coreui/angular'- pass the template name as a string to
[cTemplateId]directive
For tableData pass the following variables:
column(column definition object from [columns] array)columnName(‘key’ - derived from [columns] definition)item(current row of [items] data array)tdContent(default content for every column of a current row)
<ng-template
cTemplateId="tableData"
let-column="column"
let-columnName="columnName"
let-item="item"
let-tdContent="tdContent"
>
...
</ng-template>Having columnName you can ngSwitch for custom rendering specified columns. Do not forget about ngSwitchDefault with tdContent.
Also - SmartTableComponent has exportAs: cSmartTable that you can use as a template variable (#smartTable in the example)
- SharedModule - [cTemplateId]
- ButtonModule - [cButton]
- TableModule - [cTableActive], [cTableColor]
- UtilitiesModule - [cAlign]
