Toolbar
Beta

Table

Schema-based table component built for type-safe column definitions and dynamic cell rendering. Columns are defined in TypeScript using a cell factory — plain strings for simple values, aliased components for common patterns, and direct component references for custom renderers. Filter state, sorting, and pagination are owned by the consumer as signals, keeping the table free of business logic.

Import

import {FktTableComponent} from "frakton-ng/table";

Display

Visual presentation variants for the table: animated loading placeholders while data is in-flight, alternating row stripes for easier row tracking, full grid lines for spreadsheet-style density, row-level conditional coloring via classesFn and CSS custom properties, cell padding density presets via size, and rows pinned to the top of the scroll area via fktTableFrozenRows.

Basic

Starting point. Static data, no server interaction. Covers the three fundamental cell output formats — plain text, registered alias, and directly referenced component — and demonstrates noResults configuration for the empty state.


Loading

The [loading] input replaces table rows with animated skeleton placeholders while data is being fetched. Use [skeletonLines] to control how many placeholder rows appear (default 10).

Pair with Angular's resource() API: bind [loading]="response.isLoading()" and pass the resolved value to [data]. A reload button in [fktTableToolbar] can call response.reload() to trigger a fresh fetch.


    

Striped

The striped attribute applies an alternating background to odd-indexed rows, making wide tables easier to scan across columns.

Striping is controlled by the --fkt-table-stripe-color CSS custom property (default: --fkt-color-neutral-50). The stripe is suppressed on selected rows so the selection highlight takes precedence.


Grid lines

The gridLines attribute draws both vertical column separators and horizontal row borders, turning the table into a full spreadsheet-style grid.

Controlled by --fkt-table-column-border-color for the line color (defaults to the same value as the row border). When disabled, the table returns to the default style where only row borders between non-last rows are visible.


Conditional style

Row-level styling via [classesFn]. The function receives each row item and returns a CSS class string applied to the <tr> element.

Because Angular's view encapsulation prevents parent styles from reaching child component internals, the table exposes a --fkt-row-cell-background CSS custom property on the row host. Set this variable on the class applied to <tr> — CSS custom properties cascade through component boundaries — and the cell background will pick it up automatically.

Use ::ng-deep on a wrapper element in your component template to target the <tr> without needing ViewEncapsulation.None.


Size

The size input controls cell padding density across the whole table — header and body cells — through a three-level preset: sm, md (default), and lg.

Presets use a private CSS custom property (--_table-cell-padding) as the fallback layer, so the public override token --fkt-table-header-cell-padding always takes precedence. You can use size as a baseline and still fine-tune individual instances with the token.

When using size with fktTableVirtualScroll, update [rowHeight] to match the new row height since virtual scroll needs a fixed row height for spacer calculations.


    

Frozen rows

The fktTableFrozenRows directive pins a set of rows to the top of the table, keeping them visible while the rest of the content scrolls. Apply it to <fkt-table> and pass the rows to keep frozen via [frozenData].

Frozen rows are managed entirely by the consumer: pass a separate array for [frozenData] and a filtered array for [data]. This keeps the table free of internal pin state, so frozen rows remain correct across pagination, filtering, and live data updates without the table needing to remember anything.

A visual separator border is drawn below the frozen section. Customize it with --fkt-table-frozen-separator-color and --fkt-table-frozen-separator-width.


Cell rendering

Columns control how each cell is rendered through the cell factory returned by defineCells(). Define this once globally — typically in a shared file for the whole app or per feature module — passing an alias map that binds short keys to component classes:

import { defineCells } from 'frakton-ng/table';

const cell = defineCells({
    tag: FktTagComponent,
    actions: FktButtonsListComponent,
    // ...other cells you want to register
});

The factory exposes four rendering strategies — plain string, registered alias, custom component, and inline template — each shown in a dedicated example below.

Cell alias

A plain string is the baseline — return one directly from cell for any text value. For cells that render a component, the alias strategy references it by a short key from the map passed to defineCells():

{ key: 'status', header: 'Status', cell: (user) => cell.tag({ label: user.status }) }

Props are statically typed against the registered component's inputs — mismatches are caught at compile time.


Custom cell components

cell.custom(Component, props) mounts any Angular component directly inside the cell. Props are typed against the component's input() and model() signals — no @Input() decorator, no template wiring. The table instantiates the component and applies the bindings automatically.

cell: (product) => cell.custom(MyComponent, { ...props });

This example uses two custom cells: ImageCellComponent for a product thumbnail and RatingCellComponent for a star-rating display.


Template ref

cell.template(templateRef, context) renders an inline TemplateRef from the parent component. The context object is available in the template as $implicit and any additional named keys you pass.

cell: (product) => cell.template(this.templateRef, { ...context });

This is the right choice when the cell content is tightly coupled to parent state — a local signal, a specific formatter, a computed value — and creating a standalone component would be overkill. viewChild gives you a typed reference to any <ng-template> in the host template.


    
{{ name }} order {{ since }}
${{ amount.toFixed(2) }} {{ statusInfo[status].label }}

Custom headers

Column headers are plain strings by default. Setting header to a function returning a TemplateRef lets you render any markup in the header cell — a tooltip, a column aggregate, a label derived from parent state.

This example shows aggregates computed from the loaded data (total users, average age) displayed directly in the relevant column headers.


    
Name {{ totalUsers() }} users
Age Average {{ averageAge() }}

Interaction

Interaction patterns: row click for navigation or detail panels, multi-row selection with cross-page persistence and a select-all sentinel, and expandable row panels for inline detail rendering.

Row click

Row click via the (rowClick) output. Emits the full typed row item — no index, no template reference. Add [clickableRows]="true" to show a pointer cursor. Common use case: navigating to a detail view or opening a side panel.

@if (selectedUser(); as user) {
    
Name {{ user.name }}
Email {{ user.email }}
Age {{ user.age }}
}

Row selection

Row selection via the fktTableSelection directive. Add FktTableSelectionDirective to imports and apply fktTableSelection to <fkt-table>. Selection state lives in [(selection)] and persists across pages — the table tracks selected items by ID.

When every item on a page is checked, a banner appears offering to extend the selection to all N items in the dataset. This uses a { selectAll: true } sentinel so the consumer can delegate the operation to the backend without holding every ID in memory. Pass [totalItems] to show the exact count in the banner.

{{ selectionLabel() }} @if (hasSelection()) { }

Expandable rows

Expandable rows via the fktTableExpand directive. Add FktTableExpandDirective to imports and pass your expand template to [fktTableExpand] on <fkt-table>. The template is instantiated lazily — only when the row is first opened — and has full closure access to the parent component's signals, services, and methods.

Set [singleExpand]="true" on the table for accordion behavior (opening one row closes the others). Expanded row IDs are tracked in [(expandedRowIds)] for external control.


    
        
Order number {{ order.orderNumber }}
Customer {{ order.customer }}
Date {{ order.date | date: 'mediumDate' }}
Items {{ order.items }}
Status
Total {{ order.total | currency }}

Data

Data management patterns: column sorting via event emission, column filters using built-in or custom filter components, and server-side pagination — all driven by consumer-owned signals with no internal state in the table.

Sorting

Column sorting via the (sort) output. The table emits an FktTableSortEvent — the column key and direction (asc | desc) — then waits. The consumer updates a signal, refetches, and passes the new data back in. No internal sort logic, no opinion on multi-column sorting. Add allowSorting: true to any column definition to opt it in.


    

Filtering

Filters are declared per-column using the filter factory returned by defineFilters(). Like defineCells, define it once globally with an alias map of filter components. Attaching a filter to a column is then one line:

{ key: 'name', header: 'Name', filter: filter.text('name', { label: 'Search' }) }

Filter state flows through [(filters)] as a plain object owned by the consumer — easy to debounce, serialize, URL-sync, or pass directly to an API.

Four built-in types: filter.text(), filter.select(), filter.number() (with =, <, >, , modifiers), and filter.dateRange().

{{ filters() | json }}

Custom filter

filter.custom(key, Component, config) plugs any Angular component into the filter popup. The component integrates with the same apply/reset lifecycle as built-in filters.

Use this when none of the four built-in types fit — multi-select trees, autocomplete pickers, range sliders, or any domain-specific control. This example implements a multi-select category filter that replaces the standard single-select.

{{ filters() | json }}

Pagination

Server-side pagination with Angular's resource() API. Page state lives in the parent as a signal — the table renders exactly what it receives and emits nothing. Changing currentPage triggers a new resource load, which fetches the next slice and updates the data array.


    

Columns

Column display and layout features: sticky pinned columns at either edge, signal-driven visibility toggle, drag-and-drop reordering, and resizable column widths — with two-way bindings on all state for easy persistence.

Pinned columns

Sticky columns via pinned: 'left' | 'right' in the column definition. Left-pinned columns stick to the left edge during horizontal scroll; right-pinned columns stick to the right.

Add width to each pinned column so that offsets for subsequent pinned columns are computed correctly. A shadow marks the boundary between pinned and scrollable content.


Column toggle

Dynamic column visibility driven by a signal. Maintain a list of column toggle states and derive the visible columns array with computed(). Place the checkboxes in [fktTableToolbar] to keep the controls adjacent to the table.

Because columns is a signal-derived value, the table reacts automatically when visibility changes — no imperative update calls needed.


    
@for (toggle of columnToggles(); track toggle.key) { }

Column reorder

Drag-and-drop column reordering via the fktTableReorder directive. Add FktTableReorderDirective to imports and apply fktTableReorder to <fkt-table>. Any non-pinned header becomes draggable. The new order is tracked in [(columnOrder)] as an array of column keys — persist it to restore the layout across sessions.


Column resizing

Column width resizing via the fktTableResize directive. Add FktTableResizeDirective to imports and apply fktTableResize to <fkt-table>. A drag handle appears on the right edge of each header cell.

mode="fit" redistributes width between the dragged column and its right neighbor, keeping the total table width fixed. mode="dynamic" (default) lets the table grow freely. New widths are tracked in [(columnWidths)] keyed by column key — persist them to restore user layout preferences.


Advanced

Advanced integration patterns: CSV export via the fktTableExport directive, DOM virtualization for large datasets with fktTableVirtualScroll, and persisting the full table layout — widths, order, filters — to localStorage using effect().

Export

CSV export via the fktTableExport directive. Add FktTableExportDirective to imports, apply fktTableExport to <fkt-table>, and expose it with #exporter="fktTableExport". Call exporter.exportCsv(filename?) from any button — typically placed in [fktTableToolbar].

By default every column is included. Set exportable: false on a column definition to exclude it. Use exportValue to provide a plain-text export value when the cell renders a component (e.g. a tag or badge). Use exportHeader to override the header label in the CSV when the column uses a custom header template.


    

Virtual scroll

DOM virtualization via the fktTableVirtualScroll directive. Add FktTableVirtualScrollDirective to imports and apply fktTableVirtualScroll to <fkt-table>. Only the rows visible in the viewport (plus a small buffer) are in the DOM — the rest are spacer elements that maintain the correct scroll height.

Requires a constrained height on the table host (via --fkt-table-max-height CSS variable or an explicit height on the host). Set [rowHeight] to match the actual row height in pixels (default 48). All rows must have a uniform height.


Stateful

Persisting table layout and filter state to localStorage using effect(). Column widths ([(columnWidths)]), column order ([(columnOrder)]), and filter values ([(filters)]) are all two-way-bound signals — reading or writing them triggers Angular's reactive graph just like any other signal.

An effect() watches all three signals and serializes them to localStorage on every change. On component init the saved value is read back and used as the initial signal value. A "Reset layout" button clears the stored entry and resets all signals to their defaults.