npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@libs-ui/components-table

v0.2.357-4

Published

> Component hiển thị dữ liệu dạng bảng mạnh mẽ với Virtual Scrolling, Ghim cột, Phân trang và kết nối API trực tiếp.

Downloads

4,506

Readme

@libs-ui/components-table

Component hiển thị dữ liệu dạng bảng mạnh mẽ với Virtual Scrolling, Ghim cột, Phân trang và kết nối API trực tiếp.

Giới thiệu

LibsUiComponentsTableComponent là giải pháp toàn diện để hiển thị danh sách dữ liệu lớn. Tích hợp sẵn Virtual Scrolling (tối ưu hàng nghìn bản ghi), ghim cột (Sticky Columns), collapse/expand row và cơ chế kết nối trực tiếp với API service qua httpRequestData.

Tính năng

  • Ghim cột (Sticky Columns): Cột quan trọng luôn hiển thị qua headerLeft.
  • Virtual Scrolling: Tối ưu hiệu năng cho danh sách hàng nghìn bản ghi.
  • Phân trang: Hỗ trợ scroll-infinityclick-pagination.
  • Virtual cột ngang: Ảo hoá render cột ngang với useScrollMeasureColumn.
  • Collapse/Expand Row: Mở rộng xem chi tiết dữ liệu cấp dưới.
  • Checkbox Selection: Chọn một hoặc nhiều hàng.
  • Footer tổng hợp: Footer có thể gọi API riêng, đặt trên hoặc dưới.
  • Bar Buttons: Thanh công cụ xuất hiện khi có row được chọn.
  • Sorting: Sort local hoặc server-side.
  • Angular Signals + OnPush: Hiệu năng cao.

Khi nào sử dụng

  • Hiển thị danh sách dữ liệu lớn cần Virtual Scroller.
  • Bảng nhiều cột cần ghim cột quan trọng (headerLeft / headerRight).
  • Cần Footer tổng hợp, Bar Buttons, hoặc tính năng Collapse/Expand row.
  • Kết nối trực tiếp API service qua httpRequestData.

Cài đặt

npm install @libs-ui/components-table

Import

import { LibsUiComponentsTableComponent } from '@libs-ui/components-table';

@Component({
  standalone: true,
  imports: [LibsUiComponentsTableComponent],
})
export class YourComponent {}

Ví dụ sử dụng

1. Standard Table (Virtual Scrolling + httpRequestData)

<libs_ui-components-table
  class="h-full block"
  style="display: block; height: 100%"
  [headerRight]="headerStandard"
  [httpRequestData]="userRequestConfig()"
  (outSort)="handlerSort($event)"
/>
import { LibsUiComponentsTableComponent, ITableHeaderConfig, ISortEvent } from '@libs-ui/components-table';
import { IHttpRequestConfig } from '@libs-ui/services-http-request';
import { IAvatarConfig } from '@libs-ui/components-avatar';
import { signal } from '@angular/core';
import { of } from 'rxjs';

readonly headerStandard: Array<ITableHeaderConfig> = [
  {
    label: 'ID',
    orderby: 'id',
    hasSort: true,
    ngClass: { 'w-1/5': true },
    colTemplateConfig: [{ cssWrapper: 'font-mono text-gray-400', fieldsConfig: [{ field: 'id' }] }],
  },
  {
    label: 'Avatar',
    ngClass: { 'w-1/5': true },
    colTemplateConfig: [{
      cssWrapper: 'justify-center',
      fieldsConfig: [{
        field: 'staff_info',
        instance: 'avatar',
        getAvatarConfig: (data) => {
          // data.value có thể là Signal (data wrap qua convertObjectToSignal / httpRequestData)
          // hoặc object thường → unwrap an toàn; fallback lấy từ row (data.item).
          const unwrap = (v: unknown) => (typeof v === 'function' ? (v as () => unknown)() : v);
          const staffInfo = unwrap((unwrap(data.item) as Record<string, unknown>)?.['staff_info']) ?? unwrap(data.value);
          // Không có dữ liệu avatar → trả false để ẩn HẲN ô (trả undefined sẽ bị render thành icon rỗng).
          if (!staffInfo) return of(false as unknown as IAvatarConfig);
          // link_avatar rỗng/sai → avatar tự fallback sang chữ initials nhờ idGenColor + textAvatar.
          return of({
            linkAvatar: (staffInfo as { link_avatar?: string }).link_avatar,
            idGenColor: (staffInfo as { username?: string }).username,
            textAvatar: (staffInfo as { name?: string }).name,
            size: 32,
          });
        },
      }],
    }],
  },
  {
    label: 'Name',
    orderby: 'name',
    hasSort: true,
    ngClass: { 'w-1/5': true },
    colTemplateConfig: [{ cssWrapper: 'font-semibold', fieldsConfig: [{ field: 'name' }] }],
  },
];

readonly userRequestConfig = signal<IHttpRequestConfig>({
  objectInstance: new MockUserTableService(),
  functionName: 'list',
  argumentsValue: [new HttpParams()],
});

handlerSort(event: ISortEvent): void {
  event.stopPropagation();
  console.log('Sort changed:', event.sort);
}

2. Click Pagination

<libs_ui-components-table
  class="h-full block"
  style="display: block; height: 100%"
  [headerRight]="headerStandard"
  [httpRequestData]="userRequestConfigPagination()"
  [activityLoadData]="'click-pagination'"
  [paginationSetting]="{
    showTotalPage: true,
    showInputGotoPage: true,
    numberPageDisplay: 5
  }"
  (outSort)="handlerSort($event)"
/>
import { LibsUiComponentsTableComponent, ITableHeaderConfig } from '@libs-ui/components-table';
import { signal } from '@angular/core';
import { HttpParams } from '@angular/common/http';

readonly userRequestConfigPagination = signal({
  objectInstance: new MockUserTableService(),
  functionName: 'list',
  argumentsValue: [new HttpParams()],
});
// MockUserTableService.list() nhận params { page, pageSize }
// và phải trả về: { data: [...], paging: { page, pageSize, total_items, total_pages } }

3. Pinned Columns & Footer

Ghim cột bên trái, hiển thị Footer tổng hợp với API riêng.

<libs_ui-components-table
  [headerLeft]="headerLeft"
  [headerRight]="headerRight"
  [customPositionFooter]="'top'"
  [footerLeft]="footerLeft"
  [footerRight]="footerRight"
  [showFooter]="true"
  [httpRequestDataFooter]="httpRequestDataFooter"
  [newData]="data"
  [barButtons]="barButtons"
  (outClickBarButton)="handlerClickBarButton($event)"
/>
import {
  LibsUiComponentsTableComponent,
  ITableHeaderConfig,
  ITableFooterConfig,
  IButtonBarEvent,
  TYPE_NEW_DATA_TABLE,
} from '@libs-ui/components-table';
import { IButton } from '@libs-ui/components-buttons-button';
import { IHttpRequestConfig } from '@libs-ui/services-http-request';
import { convertObjectToSignal } from '@libs-ui/utils';

headerLeft: Array<ITableHeaderConfig> = HeaderLeft();
headerRight: Array<ITableHeaderConfig> = headerRight();
footerLeft: Array<ITableFooterConfig> = FooterLeft();
footerRight: Array<ITableFooterConfig> = FooterRight();
data: TYPE_NEW_DATA_TABLE = { data: convertObjectToSignal(DATA()) };
barButtons: Array<IButton> = BarButtons();
httpRequestDataFooter: IHttpRequestConfig = {
  objectInstance: ItemFooter(),
  functionName: 'footerData',
  argumentsValue: [],
};

handlerClickBarButton(event: IButtonBarEvent): void {
  event.stopPropagation();
  console.log('Keys selected:', event.keys);
  event.hiddenBarButtons();
}

4. Advanced Layout (Custom Header Height + Footer Position)

<libs_ui-components-table
  [headerLeft]="headerLeft"
  classHeaderContainerInclude="!h-[80px]"
  [headerRight]="headerRight"
  [customPositionFooter]="'bottom'"
  [footerLeft]="footerLeft"
  [footerRight]="footerRight"
  [showFooter]="true"
  [httpRequestDataFooter]="httpRequestDataFooter"
  [newData]="data"
  [barButtons]="barButtons"
  (outClickBarButton)="handlerClickBarButton($event)"
/>
import { LibsUiComponentsTableComponent } from '@libs-ui/components-table';
// classHeaderContainerInclude="!h-[80px]" → tuỳ chỉnh chiều cao header
// customPositionFooter="'bottom'" → footer hiển thị phía dưới bảng

5. Collapse / Expand Row (KPI Report)

<libs_ui-components-table
  class="h-full block overflow-hidden"
  style="display: block; width: 100%"
  [headerLeft]="kpiHeadersLeft"
  [headerRight]="kpiHeadersRight"
  [newData]="kpiData"
  [ignoreBar]="true"
  [configTemplateItemCollapseExpand]="kpiExpandConfig"
  [ignoreCalculatorWidthHeader]="{ headerRight: true }"
/>
import {
  LibsUiComponentsTableComponent,
  ITableHeaderConfig,
  IConfigTemplateItemCollapseExpand,
  TYPE_NEW_DATA_TABLE,
} from '@libs-ui/components-table';
import { convertObjectToSignal } from '@libs-ui/utils';
import { of } from 'rxjs';

kpiHeadersLeft: Array<ITableHeaderConfig> = KpiReportHeadersConfig(toggleExpand).slice(0, 1);
kpiHeadersRight: Array<ITableHeaderConfig> = KpiReportHeadersConfig(toggleExpand).slice(1);
kpiExpandConfig: IConfigTemplateItemCollapseExpand = {
  fieldGetDataExpand: 'expand_data',
  colTemplateConfig: [{
    cssWrapper: 'flex w-full',
    fieldsConfig: [{
      field: 'children',
      parseValue: (data) => {
        const rows = data.value() || [];
        const html = rows.map((r: any) => `<div class="p-2">${r.name}</div>`).join('');
        return of(`<div class="bg-gray-50 p-4">${html}</div>`);
      },
    }],
  }],
};
kpiData: TYPE_NEW_DATA_TABLE = {
  data: convertObjectToSignal([
    { id: 1, name: 'KPI 1', specificExpand: false, expand_data: [{ children: [{ name: 'Sub KPI A' }] }] },
  ]),
};

toggleExpand(item: any): void {
  item.update((val: any) => ({ ...val, specificExpand: !val.specificExpand }));
}

⚠️ Collapse/Expand: Mỗi item trong data phải có trường specificExpand: boolean để điều hướng state ẩn/hiện. Trường dữ liệu mở rộng được khai báo qua fieldGetDataExpand.


6. Profile List (Avatar + Badge + Chips + Context Menu)

<libs_ui-components-table
  class="h-full block"
  style="display: block; height: 100%"
  [headerLeft]="profileListHeaderLeft"
  [headerRight]="profileListHeaderRight"
  [newData]="profileListData"
/>
import { LibsUiComponentsTableComponent, ITableHeaderConfig, TYPE_NEW_DATA_TABLE } from '@libs-ui/components-table';
import { convertObjectToSignal } from '@libs-ui/utils';

readonly profileListHeaderLeft: Array<ITableHeaderConfig> = profileListHeaderLeft((data) => this.profileModal.set(data));
readonly profileListHeaderRight: Array<ITableHeaderConfig> = profileListHeaderRight();
readonly profileListData: TYPE_NEW_DATA_TABLE = {
  data: convertObjectToSignal(profileListMockData()),
};
// profileListMockData() trả về mảng profile có: fullName, source, phone, identifier,
// altEmails, email, gender, isAi (badge), tags (chip list)

7. XSS Escaped Content

<libs_ui-components-table
  class="h-full block"
  style="display: block; height: 100%"
  [headerRight]="xssEscapedHeader"
  [newData]="xssEscapedData"
  [ignoreBar]="true"
/>
import { LibsUiComponentsTableComponent, ITableHeaderConfig, TYPE_NEW_DATA_TABLE } from '@libs-ui/components-table';
import { escapeHtml } from '@libs-ui/utils';
import { convertObjectToSignal } from '@libs-ui/utils';
import { of } from 'rxjs';

readonly xssEscapedHeader: Array<ITableHeaderConfig> = [
  {
    label: 'Input người dùng',
    colTemplateConfig: [{
      cssWrapper: '',
      fieldsConfig: [{
        field: 'rawInput',
        parseValue: (data) => {
          const escaped = escapeHtml(data.value as string);
          return of(
            `<span style="color:#166534;background:#f0fdf4;padding:3px 10px;
             border-radius:4px;font-size:11px;font-family:monospace;
             border:1px solid #86efac;">${escaped}</span>`
          );
        },
      }],
    }],
  },
];

readonly xssEscapedData: TYPE_NEW_DATA_TABLE = {
  data: convertObjectToSignal([
    { id: '1', rawInput: '<script>alert("XSS")</script>' },
    { id: '2', rawInput: '<img src=x onerror=alert(1)>' },
  ]),
};

⚠️ Bảo mật: Luôn dùng escapeHtml() từ @libs-ui/utils trước khi nhúng nội dung người dùng vào parseValue trả về HTML.


8. Performance Stress (1000 cột × 30 dòng)

<libs_ui-components-table
  class="h-full block"
  style="display: block; height: 100%"
  [headerRight]="headerPerformanceStress"
  [newData]="dataPerformanceStress"
  [ignoreCalculatorWidthHeader]="{ headerRight: true }"
  [optimizeTableRenderByOnViewport]="true"
  [useScrollMeasureColumn]="true"
  [functionGetWidthItem]="handlerGetWidthItem"
  [activityLoadData]="'click-pagination'"
/>
import { LibsUiComponentsTableComponent, ITableHeaderConfig, TYPE_NEW_DATA_TABLE } from '@libs-ui/components-table';
import { convertObjectToSignal } from '@libs-ui/utils';
import { TYPE_OBJECT } from '@libs-ui/interfaces-types';

readonly COL_COUNT = 1000;
readonly ROW_COUNT = 30;

public handlerGetWidthItem = async () => 64; // px — phải khớp min-w / max-w trong ngClass header

readonly headerPerformanceStress: Array<ITableHeaderConfig> = Array.from(
  { length: this.COL_COUNT },
  (_, i) => ({
    label: `C${i + 1}`,
    ngClass: { 'shrink-0 min-w-[64px] max-w-[64px]': true },
    colTemplateConfig: [{
      cssWrapper: 'text-xs font-mono truncate max-w-[64px]',
      fieldsConfig: [{ field: `f${i}` }],
    }],
  })
);

readonly dataPerformanceStress: TYPE_NEW_DATA_TABLE = {
  data: convertObjectToSignal(
    Array.from({ length: this.ROW_COUNT }, (_, row) => {
      const obj: TYPE_OBJECT = { id: row + 1 };
      for (let c = 0; c < this.COL_COUNT; c++) obj[`f${c}`] = `${row + 1}-${c + 1}`;
      return obj;
    })
  ),
};

⚠️ Performance: Khi dùng useScrollMeasureColumn, bắt buộc truyền functionGetWidthItem trả về kích thước cột (px). Kết hợp [ignoreCalculatorWidthHeader]="{ headerRight: true }" để bỏ tính toán chiều rộng tự động.


9. Pinned Columns + Virtual Cột Ngang (useScrollMeasureColumn)

<libs_ui-components-table
  class="h-full block"
  style="display: block; height: 100%"
  [headerLeft]="headerLeftPinnedScroll"
  [headerRight]="headerRightPinnedScroll"
  [newData]="dataPinnedScroll"
  [ignoreCalculatorWidthHeader]="{ headerRight: true }"
  [optimizeTableRenderByOnViewport]="true"
  [useScrollMeasureColumn]="true"
  [functionGetWidthItem]="handlerGetWidthItem"
  [activityLoadData]="'click-pagination'"
/>
import { LibsUiComponentsTableComponent, ITableHeaderConfig, TYPE_NEW_DATA_TABLE } from '@libs-ui/components-table';
import { convertObjectToSignal } from '@libs-ui/utils';
import { TYPE_OBJECT } from '@libs-ui/interfaces-types';

readonly RIGHT_COL_COUNT = 3000;
readonly ROW_COUNT = 50;

public handlerGetWidthItem = async () => 64;

readonly headerLeftPinnedScroll: Array<ITableHeaderConfig> = [
  {
    label: 'ID',
    ngClass: { 'shrink-0 min-w-[80px] max-w-[80px]': true },
    colTemplateConfig: [{ cssWrapper: 'text-xs font-mono text-gray-500 truncate max-w-[80px]', fieldsConfig: [{ field: 'id' }] }],
  },
  {
    label: 'Name',
    ngClass: { 'shrink-0 min-w-[160px] max-w-[160px]': true },
    colTemplateConfig: [{ cssWrapper: 'text-xs font-semibold truncate max-w-[160px]', fieldsConfig: [{ field: 'name' }] }],
  },
];

readonly headerRightPinnedScroll: Array<ITableHeaderConfig> = Array.from(
  { length: this.RIGHT_COL_COUNT },
  (_, i) => ({
    label: `M${i + 1}`,
    ngClass: { 'shrink-0 min-w-[64px] max-w-[64px]': true },
    colTemplateConfig: [{ cssWrapper: 'text-xs font-mono truncate max-w-[64px]', fieldsConfig: [{ field: `m${i}` }] }],
  })
);

readonly dataPinnedScroll: TYPE_NEW_DATA_TABLE = {
  data: convertObjectToSignal(
    Array.from({ length: this.ROW_COUNT }, (_, row) => {
      const obj: TYPE_OBJECT = { id: row + 1, name: `User ${row + 1}` };
      for (let c = 0; c < this.RIGHT_COL_COUNT; c++) obj[`m${c}`] = `${row + 1}-${c + 1}`;
      return obj;
    })
  ),
};

API Documentation

@Input()

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | [httpRequestData] | IHttpRequestConfig | undefined | (Khuyên dùng) Cấu hình service để component tự gọi API, phân trang, sort. | [httpRequestData]="requestConfig" | | [httpRequestDataFooter] | IHttpRequestConfig | undefined | Cấu hình service gọi API riêng cho Footer. | [httpRequestDataFooter]="footerConfig" | | [headerLeft] | Array<ITableHeaderConfig> | [] | Cột ghim cố định bên trái (Sticky). | [headerLeft]="pinnedCols" | | [headerRight] | Array<ITableHeaderConfig> | [] | Cột chính hiển thị trong vùng cuộn. | [headerRight]="mainCols" | | [footerLeft] | Array<ITableFooterConfig> | undefined | Cấu hình footer phần ghim trái. | [footerLeft]="footerLeftCols" | | [footerRight] | Array<ITableFooterConfig> | undefined | Cấu hình footer phần cuộn. | [footerRight]="footerRightCols" | | [showFooter] | boolean | undefined | Hiển thị footer của bảng. | [showFooter]="true" | | [customPositionFooter] | 'top' \| 'bottom' | 'bottom' | Vị trí footer (trên hoặc dưới bảng). | [customPositionFooter]="'top'" | | [ignoreBorderFooter] | boolean | undefined | Ẩn border của footer. | [ignoreBorderFooter]="true" | | [newData] | TYPE_NEW_DATA_TABLE | undefined | Dữ liệu tĩnh đẩy thẳng vào bảng (không dùng httpRequestData). | [newData]="{ data: convertObjectToSignal(list) }" | | [filter] | TYPE_DATA_FILTER_TABLE | { filterData: {} } | Filter/search. Thay đổi filterData để trigger gọi API lại. | [filter]="{ filterData: { keySearch: 'abc' } }" | | [activityLoadData] | 'scroll-infinity' \| 'click-pagination' | 'scroll-infinity' | Chế độ tải trang: cuộn vô hạn hoặc click phân trang. | [activityLoadData]="'click-pagination'" | | [paginationSetting] | IPaginationConfig | undefined | Tuỳ chỉnh giao diện phân trang (số trang, ô nhảy trang, ...). | [paginationSetting]="{ showTotalPage: true }" | | [fieldKey] | string | 'id' | Trường định danh duy nhất mỗi hàng. | [fieldKey]="'userId'" | | [barButtons] | Array<IButton> | undefined | Nút thao tác xuất hiện trên bar khi có row được chọn. | [barButtons]="actionButtons" | | [configTemplateItemCollapseExpand] | IConfigTemplateItemCollapseExpand | undefined | Cấu hình template Collapse/Expand row. | [configTemplateItemCollapseExpand]="expandConfig" | | [noDataConfig] | ITableNoDataConfig | {textNoData: 'i18n_no_data_yet', textSearchNoData: 'i18n_no_result'} | Cấu hình text/icon khi không có dữ liệu. | [noDataConfig]="{ textNoData: 'Chưa có dữ liệu' }" | | [useScrollMeasureColumn] | boolean | undefined | Bật Virtual Scroll cột ngang (ảo hoá render cột). | [useScrollMeasureColumn]="true" | | [functionGetWidthItem] | () => Promise<number> | undefined | (Bắt buộc khi useScrollMeasureColumn=true) Hàm trả về px mỗi cột. | [functionGetWidthItem]="getColWidth" | | [ignoreCalculatorWidthHeader] | { headerLeft?: boolean; headerRight?: boolean } | undefined | Bỏ qua tính toán chiều rộng tự động (dùng khi nhiều cột). | [ignoreCalculatorWidthHeader]="{ headerRight: true }" | | [optimizeTableRenderByOnViewport] | boolean | undefined | Tối ưu render cell khi nằm trong viewport. | [optimizeTableRenderByOnViewport]="true" | | [bufferAmount] | number | 5 | Số item buffer cho Virtual Scroller (tối đa 25). | [bufferAmount]="10" | | [enableUnequalChildrenSizes] | boolean | false | Hỗ trợ item có kích thước không đều (Virtual Scroller). | [enableUnequalChildrenSizes]="true" | | [totalItem] | number | undefined | Tổng số item (override tính toán từ paging). | [totalItem]="500" | | [defaultKeysSelected] | Array<any> | undefined | Danh sách key được chọn mặc định khi khởi tạo. | [defaultKeysSelected]="['1','2']" | | [maxItemSelected] | number | 100 | Số lượng item tối đa được chọn. | [maxItemSelected]="50" | | [autoEmitKeysSelectedWithItems] | boolean | undefined | Khi true, outKeysSelected emit cả items (không chỉ keys). | [autoEmitKeysSelectedWithItems]="true" | | [configSelectMoreItem] | IConfigSelectMoreItem | undefined | Dropdown chọn số lượng item từ droplist (tương tự "Chọn X bản ghi"). | [configSelectMoreItem]="moreItemConfig" | | [disableCheckbox] | boolean | false | Vô hiệu hoá tất cả checkbox. | [disableCheckbox]="isReadOnly()" | | [sortLocal] | boolean | undefined | Sort tại client, không gọi API. | [sortLocal]="true" | | [filterOrSortLocal] | TYPE_TABLE_FILTER | undefined | Hàm filter/sort local tuỳ chỉnh. | [filterOrSortLocal]="customFilterFn" | | [ignoreBar] | boolean | undefined | Ẩn toàn bộ bar (kể cả thanh đếm). | [ignoreBar]="true" | | [ignoreClassBgHeader] | boolean | undefined | Bỏ class bg-white mặc định của header. | [ignoreClassBgHeader]="true" | | [isDashBorder] | boolean | undefined | Đường viền dạng nét đứt. | [isDashBorder]="true" | | [ignoreBorderItem] | boolean | undefined | Ẩn border giữa các row. | [ignoreBorderItem]="true" | | [ignoreBorderItemLast] | boolean | undefined | Ẩn border của row cuối. | [ignoreBorderItemLast]="true" | | [isHiddenHeaderWhenNodata] | boolean | undefined | Ẩn header khi không có dữ liệu. | [isHiddenHeaderWhenNodata]="true" | | [showScrollTablePinnedIfNoData] | boolean | undefined | Vẫn hiển thị phần cuộn khi không có data. | [showScrollTablePinnedIfNoData]="true" | | [onlyShowNoResult] | boolean | undefined | Chỉ hiển thị trạng thái "không có kết quả" (không hiển thị "không có dữ liệu"). | [onlyShowNoResult]="true" | | [templateNoData] | TemplateRef | undefined | Custom template khi không có dữ liệu. | [templateNoData]="noDataTpl" | | [templateNoResult] | TemplateRef | undefined | Custom template khi không có kết quả tìm kiếm. | [templateNoResult]="noResultTpl" | | [timeHighlighNewItem] | number | 2000 | Thời gian (ms) highlight item mới thêm vào. | [timeHighlighNewItem]="3000" | | [labelBarNoSelectItem] | string | 'i18n_total_quantity' | Label hiển thị khi không có item nào được chọn. | [labelBarNoSelectItem]="'Tổng số'" | | [labelBarButtons] | string | 'i18n_number_item_selected' | Label số item đã chọn trên bar. | [labelBarButtons]="'i18n_selected'" | | [classHeaderContainerInclude] | string | undefined | Class thêm vào container header. | classHeaderContainerInclude="!h-[80px]" | | [classHeaderLeftInclude] | string | undefined | Class thêm vào header left. | classHeaderLeftInclude="bg-blue-50" | | [classBodyInclude] | string | '' | Class thêm vào vùng body. | classBodyInclude="max-h-[400px]" | | [classFooterInclude] | string | '' | Class thêm vào footer. | classFooterInclude="font-bold" | | [classTableContainerInclude] | string | '' | Class thêm vào container bảng. | classTableContainerInclude="rounded-lg" | | [classBarInclude] | string | 'justify-between mb-[16px]' | Class thêm vào bar. | classBarInclude="justify-end" | | [classLabelBarButtons] | string | '' | Class cho label số item đã chọn. | classLabelBarButtons="text-blue-500" | | [classLabelBarNoSelectItem] | string | undefined | Class cho label khi không chọn item. | classLabelBarNoSelectItem="text-gray-500" |

@Output()

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outSort) | ISortEvent | Emit khi người dùng thay đổi sort cột. | handlerSort(e: ISortEvent): void { e.stopPropagation(); } | (outSort)="handlerSort($event)" | | (outClickBarButton) | IButtonBarEvent | Emit khi click nút trên bar (có items chọn). | handlerBarButton(e: IButtonBarEvent): void { e.stopPropagation(); e.hiddenBarButtons(); } | (outClickBarButton)="handlerBarButton($event)" | | (outKeysSelected) | { keys: Array<any>; items?: Array<WritableSignal<any>> } | Emit khi danh sách row được chọn thay đổi. | handlerKeysSelected(e: { keys: any[] }): void { e.stopPropagation(); } | (outKeysSelected)="handlerKeysSelected($event)" | | (outLoadMore) | ILoadMoreEvent | Emit khi cần tải thêm dữ liệu (scroll infinity + không dùng httpRequestData). | handlerLoadMore(e: ILoadMoreEvent): void { e.stopPropagation(); } | (outLoadMore)="handlerLoadMore($event)" | | (outLoading) | boolean | Emit trạng thái loading của component. | handlerLoading(loading: boolean): void { } | (outLoading)="handlerLoading($event)" | | (outTotalItem) | number | Emit tổng số bản ghi từ API. | handlerTotalItem(total: number): void { this.total.set(total); } | (outTotalItem)="handlerTotalItem($event)" | | (outFunctionsControl) | ITableFunctionControlEvent | Emit object chứa các hàm điều khiển bảng từ bên ngoài. | handlerFunctionsControl(ctrl: ITableFunctionControlEvent): void { this.tableCtrl = ctrl; } | (outFunctionsControl)="handlerFunctionsControl($event)" | | (outLoadDataComplete) | WritableSignal<Array<WritableSignal<any>>> | Emit sau khi gọi API thành công, trả về signal chứa toàn bộ store. | handlerLoadComplete(store: any): void { } | (outLoadDataComplete)="handlerLoadComplete($event)" | | (outLoadDataError) | IHttpResponseError | Emit khi gọi API thất bại. | handlerLoadError(err: IHttpResponseError): void { } | (outLoadDataError)="handlerLoadError($event)" | | (outScrollIsGone) | ILoadMoreEvent | Emit khi scroll biến mất (dữ liệu ít hơn chiều cao container). | handlerScrollGone(e: ILoadMoreEvent): void { } | (outScrollIsGone)="handlerScrollGone($event)" |


FunctionsControl API

Nhận về qua (outFunctionsControl) để điều khiển bảng từ component cha.

import { ITableFunctionControlEvent } from '@libs-ui/components-table';

private tableCtrl?: ITableFunctionControlEvent;

handlerFunctionsControl(ctrl: ITableFunctionControlEvent): void {
  this.tableCtrl = ctrl;
}

// Ví dụ sử dụng:
refreshTable(): void {
  this.tableCtrl?.callApiByService(true);      // Tải lại từ trang 1
}

resetTable(): void {
  this.tableCtrl?.reset(true);                 // Reset + xóa sort
}

addItemToTop(newItem: any): void {
  this.tableCtrl?.addItems(
    convertObjectToSignal([newItem]),
    true,                                      // addFirst = true
  );
}

removeItem(id: string): void {
  this.tableCtrl?.removeItems([id]);
}

clearSelection(): void {
  this.tableCtrl?.resetKeySelected();
}

getCount(): number {
  return this.tableCtrl?.getCount() ?? 0;
}

jumpToPage(page: number): void {
  this.tableCtrl?.changePagination(page);
}

| Hàm | Signature | Mô tả | |---|---|---| | reset | (clearSort?: boolean) => void | Reset toàn bộ bảng về trạng thái ban đầu. | | callApiByService | (firstCall: boolean, resetAfterCallApi?: boolean) => void | Gọi lại API. firstCall=true → tải từ trang 1. | | resetScroll | (left?: number, top?: number) => void | Reset vị trí scroll. | | addItems | (items: TYPE_DATA_TABLE, addFirst: boolean, ...) => void | Thêm items vào bảng. | | removeItems | (keys: Array<unknown>, ...) => void | Xóa items theo key. | | resetKeySelected | () => void | Xóa toàn bộ selection. | | getCount | () => number | Lấy tổng số item hiện tại. | | getStore | () => Array<WritableSignal<TYPE_OBJECT>> | Lấy toàn bộ store (kể cả item đã scroll qua). | | getItems | () => Array<WritableSignal<TYPE_OBJECT>> | Lấy items đang hiển thị. | | hightLightItem | (items: Array<TYPE_ITEM_DATA_TABLE>) => Promise<void> | Highlight item (hiệu ứng màu). | | changePagination | (page?: number) => Promise<void> | Nhảy tới trang cụ thể. | | setCountPagingStore | (count: number) => void | Cập nhật tổng số item trong paging store. | | detectChanges | () => void | Trigger change detection thủ công. |


Types & Interfaces

ITableHeaderConfig

import { ITableHeaderConfig } from '@libs-ui/components-table';

export interface ITableHeaderConfig {
  label?: string | TYPE_OBJECT;            // Label header (hỗ trợ i18n object)
  hasSort?: boolean;                        // Hiện nút sort
  orderby?: string;                         // Field dùng để sort
  hasCheckbox?: boolean;                    // Hiện checkbox
  hasCheckboxAll?: boolean;                 // Hiện checkbox "Chọn tất cả"
  disableCheckbox?: boolean;                // Disable checkbox
  colTemplateConfig?: Array<ITableTemplateConfig>;  // Cấu hình hiển thị cell
  rowsConfig?: {                            // Multi-row header
    classContainerRows?: string;
    rows: Array<{ classRow?: string; cols: Array<ITableHeaderConfigCol> }>;
  };
  ngStyle?: TYPE_OBJECT;
  ngClass?: TYPE_OBJECT;
  checkConditionCheckBoxHidden?: TYPE_FUNCTION<boolean>;
  isShowIndexOnRow?: boolean;               // Hiện số thứ tự dòng
  defaultMode?: TYPE_SORT_TYPE;             // Mode sort mặc định ('asc' | 'desc')
  labelDescription?: { content: string; classInclude?: string };
  tooltipsLeft?: Array<IPopover>;
  tooltipsRight?: Array<IPopover>;
}

ITableTemplateConfig & ITableFieldTemplateConfig

import { ITableTemplateConfig, ITableFieldTemplateConfig } from '@libs-ui/components-table';

export interface ITableTemplateConfig {
  cssWrapper: string;                       // Class CSS cho wrapper của cell
  fieldsConfig: Array<ITableFieldTemplateConfig>;
}

export interface ITableFieldTemplateConfig {
  field: string;                            // Tên field trong data object
  instance?: 'tooltip' | 'line-clamp' | 'buttons' | 'button-status'
           | 'switch' | 'badge' | 'other-action-show-popup'
           | 'shape-style' | 'image' | 'avatar' | 'button-action-show-popup';
  parseValue?: TYPE_FUNCTION;               // Custom render → trả về Observable<string> (HTML)
  action?: (dataField, item, itemSelected?) => any;
  getAvatarConfig?: TYPE_FUNCTION<IAvatarConfig | undefined>;
  buttons?: Array<WritableSignal<IButton>>; // instance = 'buttons'
  getButtonsByItem?: TYPE_FUNCTION<Array<WritableSignal<IButton>>>;
  showButtonHoverMode?: boolean;            // Chỉ hiện button khi hover
  switchAction?: (switchData: ISwitchEvent, item) => void; // instance = 'switch'
  getDisableValueSwitch?: TYPE_FUNCTION;
  getActiveValueSwitch?: TYPE_FUNCTION;
  lineClampConfig?: ILineClampConfig;       // instance = 'line-clamp'
  ngStyle?: TYPE_OBJECT;
  getNgStyle?: TYPE_FUNCTION;
  ngClass?: TYPE_OBJECT;
  getNgClass?: TYPE_FUNCTION;
  rows?: Array<ITableTemplateConfig>;       // Sub-rows trong cell
  getComponentOutlet?: () => Observable<any>;
  getDataComponentOutlet?: TYPE_FUNCTION;
}

IConfigTemplateItemCollapseExpand

import { IConfigTemplateItemCollapseExpand } from '@libs-ui/components-table';

export interface IConfigTemplateItemCollapseExpand {
  fieldGetDataExpand?: string;              // Trường chứa dữ liệu con trong mỗi row
  cssWrapper?: string;
  templateCssWrapper?: string;
  templateCssWrapperHost?: string;
  colTemplateConfig?: Array<ITableTemplateConfig>;
}
// Mỗi item trong data cần có: specificExpand: boolean (quản lý state đóng/mở)

TYPE_NEW_DATA_TABLE

import { TYPE_NEW_DATA_TABLE } from '@libs-ui/components-table';

// Cách tạo dữ liệu tĩnh:
import { convertObjectToSignal } from '@libs-ui/utils';

const data: TYPE_NEW_DATA_TABLE = {
  data: convertObjectToSignal([
    { id: '1', name: 'Item 1' },
    { id: '2', name: 'Item 2' },
  ]),
  addToLastList: false,  // true = thêm xuống cuối, false = reset và set mới
};

IHttpRequestConfig (httpRequestData)

import { IHttpRequestConfig } from '@libs-ui/services-http-request';

// Service phải có method trả về Observable với cấu trúc:
// { data: Array<any>, paging?: { page, pageSize, total_items, total_pages } }

const requestConfig: IHttpRequestConfig = {
  objectInstance: myService,          // Instance của service (inject qua inject())
  functionName: 'getList',            // Tên method trong service
  argumentsValue: [new HttpParams()], // Mảng tham số truyền vào method
};

Lưu ý quan trọng

⚠️ Container cần chiều cao xác định: Virtual Scroller cần biết chiều cao để tính số item render. Container bao ngoài phải có chiều cao cố định (VD: <div class="h-[500px]">). Nếu muốn table fill 100% container, thêm class="h-full block" vào thẻ <libs_ui-components-table>, nhưng nếu container đã đủ cao thì không cần.

⚠️ useScrollMeasureColumn: Bắt buộc kèm functionGetWidthItem trả về px đúng với min-w / max-w trong ngClass header. Thiếu sẽ tính toán sai.

⚠️ Collapse/Expand: Item data phải có trường specificExpand: boolean. Toggle bằng cách item.update(val => ({ ...val, specificExpand: !val.specificExpand })).

⚠️ httpRequestData + filter: Khi filter().filterData thay đổi, component tự động gọi lại API từ trang 1. Dùng filter.ignoreCallApiAuto = true nếu muốn tự kiểm soát.


Demo

npx nx serve core-ui

Truy cập: http://localhost:4500/table

License

MIT