Angular Smart Table Component (DataTable)

Smart Table

CoreUI PRO
This component is part of CoreUI PRO – a powerful UI library with over 250 components and 25+ templates, designed to help you build modern, responsive apps faster. Fully compatible with Angular, Bootstrap, React.js, and Vue.js.

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 usersData

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

Custom headers

  • custom table header templates
    ~4.7.7

To add custom column header template with a labelTemplateName:

  1. HTML: add ng-template with cTemplateId="columnLabel_labelTemplateName" and let-column template variable
  2. TypeScript: define IColumns[] config with _labelTemplateId and 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 :

  1. HTML: add ng-template with cTemplateId="columnFilter_columnName" with your component
  2. TypeScript: create filterFunction and pass it to the columnFilterValue prop of c-smart-table component
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 usersData
Custom filter/sorter functions with tableData template aggregated columns
~5.2.19
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.26
    you can pass an ng-template with cTemplateId="tableSummaryRow" as a c-smart-table content.
  • Style tr and th elements 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 usersData

External 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

ts
import { NgModule } from '@angular/core';
import { SharedModule, SmartTableModule } from '@coreui/angular';

@NgModule({
  imports: [SmartTableModule, SharedModule]
})
export class CustomAppModule {}

SmartTable Standalone

ts
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

jsx
import { SmartTableComponent } from '@coreui/angular-pro'

Props

PropertyDefaultType
activePage1number

Sets active page. If 'pagination' prop is enabled, activePage is set only initially.

cleanerfalseboolean

When set, displays table cleaner above table, next to the table filter (or in place of table filter if tableFilter prop is not set) Cleaner resets tableFilterValue, columnFilterValue, sorterValue. If clean is possible it is clickable (tabIndex="0" role="button", color="danger"), otherwise it is not clickable and transparent. Cleaner can be customized through the cleaner slot.

clickableRowsfalseboolean

Style table items as clickable.

columnFilter-boolean, IColumnFilter

When set, displays additional filter row between table header and items, allowing filtering by specific column. Column filter can be customized, by passing prop as object with additional options as keys. Available options: - external (Boolean) - Disables automatic filtering inside component. - lazy (Boolean) - Set to true to trigger filter updates only on change event.

columnFilterValue-IColumnFilterValue

Value of table filter. To set pass object where keys are column names and values are filter strings e.g.: { user: 'John', age: 12 }

columns-IColumn[]

Prop for table columns configuration. If prop is not defined, table will display columns based on the first item keys, omitting keys that begin with underscore (e.g. '_classes') In columns prop each array item represents one column. Item might be specified in two ways: String: each item define column name equal to item value. Object: item is object with following keys available as column configuration: - key (required)(String) - define column name equal to item key. - label (String) - define visible label of column. If not defined, label will be generated automatically based on column name, by converting kebab-case and snake_case to individual words and capitalization of each word. - _classes (String/Array/Object) - adds classes to all cells in a column - _style (String/Array/Object) - adds styles to the column header (useful for defining widths) - sorter (Boolean) - disables sorting of the column when set to false - filter (Boolean) - removes filter from column when set to false.

columnSorterfalseboolean, ISorter

Enables table sorting by column value. Sorting will be performed correctly if values in column are one of type: string (case-insensitive) or number. Sorter can be customized, by passing prop as object with additional options as keys. Available options: - external (Boolean) - Disables automatic sorting inside component. - resettable (Boolean) - If set to true clicking on sorter have three states: ascending, descending and null. That means that third click on sorter will reset sorting, and restore table to original order.

Displays table footer, which mirrors table header. (without column filter).

headertrueboolean

Set false to remove table header.

itemsitems.filter((item: { [x: string]: any }) => {IItem[]

Array of objects, where each object represents one item - row in table. Additionally, you can add style classes to each row by passing them by '_classes' key and to single cell by '_cellClasses'. Example item: { name: 'John' , age: 12, _props: { color: 'success' }, _cellProps: { age: { class: 'fw-bold'}}} For column generation description see columns prop.

itemsPerPage10number

Number of items per site, when pagination is enabled.

itemsPerPageLabel'Items per page:'string

Label for items per page selector.

itemsPerPageOptions[5, 10, 20, 50]number[]

Items per page selector options.

itemsPerPageSelect-boolean, ItemsPerPageSelect

Adds select element over table, which is used for control items per page in pagination. If you want to customize this element, pass an object with optional values: - external (Boolean) - disables automatic 'itemsPerPage' change (use to change pages externally by 'pagination-change' event).

loading-boolean

When set, table will have loading style: loading spinner and reduced opacity. When 'small' prop is enabled spinner will be also smaller.

noItemsLabel'No items found'string, TemplateRef<any>

ReactNode or string for passing custom noItemsLabel texts.

paginationfalseboolean

Enables default pagination. Set to true for default setup or pass an object with additional CPagination props. Default pagination will always have the computed number of pages that cannot be changed. The number of pages is generated based on the number of passed items and 'itemsPerPage' prop. If this restriction is an obstacle, you can make external CPagination instead.

selectable-boolean

Add checkboxes to make table rows selectable.

selectAlltrueboolean

Enables select all checkbox displayed in the header of the table.

sorterValue-ISorterValue

State of the sorter. Name key is column name, direction can be 'asc' or 'desc'.

tableBodyProps{}ITableSectionProps

Properties to TableBody component.

tableFilter-boolean, ITableFilter

When set, displays table filter above table, allowing filtering by specific column. Column filter can be customized, by passing prop as object with additional options as keys. Available options: - placeholder (String) - Sets custom table filter placeholder. - label (String) - Sets custom table filter label. - external (Boolean) - Disables automatic filtering inside component. - lazy (Boolean) - Set to true to trigger filter updates only on change event.

tableFilterLabel'Filter:' todo or remove?string

The element represents a caption for a component.

tableFilterPlaceholder'type string...' todo or remove?string

Specifies a short hint that is visible in the search input.

tableFilterValue-string

Value of table filter. Set .sync modifier to track changes.

tableFootProps{}ITableSectionProps

Properties to TableFoot component.

tableHeadProps{}ITableSectionProps

Properties to CTableHead component.

tableProps{}ITable

Properties to CTable component.

Events

Event name
activePageChange

Event emitted on activePage change.

  • $event number
cleanerClick

Event emitted on cleaner click.

  • $event void
columnFilterValueChange

Event emitted on columnFilterValue change.

  • $event IColumnFilterValue
filteredItemsChange

Event emitted on filteredItems change.

  • $event IItemInternal[]
itemsPerPageChange

Event emitted on itemsPerPage change.

  • $event number
rowClick

Event emitted on row click.

  • $event any
selectedItemsChange

Event emitted on selectedItems change.

  • $event IItem[]
sorterValueChange

Event emitted on sorterValue change.

  • $event ISorterValue
tableFilterValueChange

Event emitted on tableFilterValue change.

  • $event string

Templates

You can pass an ng-template with cTemplateId as a c-smart-table content.

Available templates with [cTemplateId] names:

  • tableCustomHeader (
    ~4.4.5
    goes to the header row with table filter/cleaner)
  • tableData (goes to td html element for specified columnName)
  • tableDetails (goes to separate row as row details, for every tr)
  • tableSummaryRow (
    ~4.5.26
    goes to the table footer)
  • columnFilter_* (see: Custom Filters)
  • columnLabel_* (
    ~4.7.7
    see: 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]