ComponentUpdated in v15

Table

Rows of data with sorting, filtering, paging and custom cells.

Installation

Import the class, and @use the entry point in your global styles: without it the component renders unstyled.

import { Component } from '@angular/core';
import { WrTable, WrTableCell, WrTableExpand, WrTableGroupHeader } from 'ngwr/table';

@Component({ imports: [WrTable, WrTableCell, WrTableExpand, WrTableGroupHeader] })
export class MyComponent {}

Basic usage

Columns are a record keyed by row property; rows are plain objects.

Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer
<wr-table [columns]="columns" [items]="rows" />

Sort + filter + custom cell

[(sort)] holds the sort, filterChange reports the filter, and [wrTableCell] replaces a column's cells.

Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer
<wr-table [columns]="columns" [items]="rows" [(sort)]="sort" (filterChange)="onFilter($event)">
  <ng-template wrTableCell="role" let-value>
    <wr-tag [color]="value === 'admin' ? 'danger' : 'info'">{{ value }}</wr-tag>
  </ng-template>
</wr-table>

One column carrying both controls

One column can have both sortable and filterItems. With a mouse, both controls are below the 24px minimum target size.

Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer
const columns: WrTableColumns = {
  name: { title: 'Name', sortable: true },
  email: { title: 'Email' },
  role: {
    title: 'Role',
    sortable: true,
    filterItems: [
      { title: 'Admin', value: 'admin' },
      { title: 'Editor', value: 'editor' },
      { title: 'Viewer', value: 'viewer' },
    ],
  },
};

<wr-table [columns]="columns" [items]="rows()" [(sort)]="sort" (filterChange)="onFilter($event)" />

Sorting is state, not a sort

The table never reorders items: sort them yourself from sort().

protected readonly sort = signal<readonly WrTableSortState[]>([]);

protected readonly rows = computed(() => {
  const rules = this.sort();
  if (rules.length === 0) return this.source();

  return [...this.source()].sort((a, b) => {
    for (const { key, direction } of rules) {
      if (!direction) continue;
      const cmp = String(a[key] ?? '').localeCompare(String(b[key] ?? ''));
      if (cmp !== 0) return direction === 'asc' ? cmp : -cmp;
    }
    return 0;
  });
});

// Server-side, send it with the request instead.
effect(() => {
  const [primary] = this.sort();
  this.load({ sortBy: primary?.key, order: primary?.direction });
});
  • sort is an array, one entry per sorted column; the first entry is the primary key.
  • Only the primary column announces aria-sort. With several sorted columns, each shows its place in the order as a number. Since v15.
  • Only the sort button sorts, not the whole header. In tests, getSortDirection() reads aria-sort; getSortIndicator() and getSortOrder() read every sorted column.
  • Sorting does not reset page. Reset it in your sort handler if you want.

What a cell template can read

let-value is the cell value and let-row="item" the row. A bare let-row binds the value again.

<ng-template wrTableCell="role" let-value let-row="item" let-col="column">
  {{ col.title }}: {{ value }} — {{ row.email }}
</ng-template>

Row actions

Give the action column a key no row property uses and an empty title, and read the row with let-row="item".

Name
Email
Roman [email protected] Delete
Alice [email protected] Delete
Bob [email protected] Delete
Cara [email protected] Delete
Diego [email protected] Delete
Restore rows
const columns: WrTableColumns = {
  name: { title: 'Name' },
  email: { title: 'Email' },
  actions: { title: '', width: 96 },
};

<wr-table [columns]="columns" [items]="rows">
  <ng-template wrTableCell="actions" let-row="item">
    <wr-btn size="sm" color="danger" outlined (click)="remove(row)">Delete</wr-btn>
  </ng-template>
</wr-table>

Loading, empty — and error

[loading] shows a spinner and emptyLabel the empty text. There is no error input: show a <wr-alert> beside the table.

<wr-table [columns]="columns" [items]="rows()" [loading]="loading()" emptyLabel="No users yet" />

<!-- An error goes beside the table. -->
@if (error(); as message) {
  <wr-alert type="danger" title="Could not load users" [message]="message" />
}
<wr-table [columns]="columns" [items]="rows()" [loading]="loading()" />

Pagination

[pageSize] pages the rows and adds a pager. Add [total] to page on the server.

Name
Email
Role
User 01 [email protected] admin
User 02 [email protected] viewer
User 03 [email protected] viewer
User 04 [email protected] editor
User 05 [email protected] viewer
1 2 3 4 5 1 / 5
<wr-table [columns]="columns" [items]="rows" [pageSize]="5" [(page)]="page" />

Server-side paging — what to bind, what fires

With [total], the table shows items as given. Listen to (pageChange), or bind [(page)].

<wr-table
  [columns]="columns"
  [items]="pageRows()"
  [total]="total()"
  [pageSize]="20"
  [page]="page()"
  (pageChange)="onPage($event)"
  [(sort)]="sort"
/>
protected onPage(page: number): void {
  this.page.set(page);
  this.load(); // limit: 20, skip: (page - 1) * 20
}
  • [pageSize] is still required: without it no pager renders.
  • The pager has no range label or size changer. For those, leave pageSize at 0 and add your own <wr-pagination>.

Virtual scroll

Set virtualScroll and viewportHeight; all rows share one height.

<wr-table
  virtualScroll
  [rowHeight]="41"
  [viewportHeight]="440"
  [columns]="columns"
  [items]="rows"
/>
ID
Name
Email
Role
1 User 00001 [email protected] admin
2 User 00002 [email protected] viewer
3 User 00003 [email protected] viewer
4 User 00004 [email protected] editor
5 User 00005 [email protected] viewer
6 User 00006 [email protected] viewer
7 User 00007 [email protected] editor
8 User 00008 [email protected] admin
9 User 00009 [email protected] viewer
10 User 00010 [email protected] editor
11 User 00011 [email protected] viewer
12 User 00012 [email protected] viewer
13 User 00013 [email protected] editor
14 User 00014 [email protected] viewer
15 User 00015 [email protected] admin
16 User 00016 [email protected] editor
17 User 00017 [email protected] viewer
18 User 00018 [email protected] viewer
  • Grouping, tree rows, expandable rows, responsive and a pager turn it off.
  • For SSR, set rowHeight: 41 at md, 32 at sm, 48 at lg, 55 at touch.

Row selection

Set rowSelection to 'multiple' or 'single', rowKey to a property or function, and bind [(selection)] to the selected keys.

<wr-table
  rowSelection="multiple"
  rowKey="email"
  [(selection)]="selected"
  [columns]="columns"
  [items]="rows"
/>
Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer
selected: 0
  • Each row's checkbox is named by an action word and the row's first cell: "Select row Roman". The same goes for the expand and tree toggles, and the group controls take the group's label. Since v15.
  • selectRowLabel and the other *Label inputs set the action word.

Expandable rows

<ng-template wrTableExpand let-row> adds a detail panel per row. Set rowKey and bind [(expanded)].

<wr-table rowKey="email" [(expanded)]="expanded" [columns]="columns" [items]="rows">
  <ng-template wrTableExpand let-row>
    <p>{{ row.name }} — {{ row.email }}</p>
  </ng-template>
</wr-table>
Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer

Summary row

A column's summary adds a footer row: 'sum', 'avg', 'count', 'min', 'max', or a function of the rows and a context.

const columns: WrTableColumns = {
  product: { title: 'Product', summary: () => 'Total' },
  price:   { title: 'Price', summary: 'avg' },
  qty:     { title: 'Qty', summary: 'sum' },
};
Product
Price
Qty
Widget 19.99 3
Gadget 49.5 1
Gizmo 8.75 12
Doohickey 120 2
Total 49.56 18

Row grouping

Set groupBy to a property or function; [(collapsedGroups)] holds the collapsed groups. Grouping runs after paging, so sort by the same key or set pageSize to 0.

<wr-table
  groupBy="role"
  [(collapsedGroups)]="collapsed"
  [columns]="columns"
  [items]="rows"
/>
Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Cara [email protected] editor
Bob [email protected] viewer
Diego [email protected] viewer

Group subtotals

groupSummary adds a subtotal row under each group. A summary function's second argument says which row it computes: scope is 'group' or 'total'. Set pageSize to 0 with it, since the footer totals every row.

const columns: WrTableColumns = {
  // One function feeds the group rows and the grand total; the context says which.
  region:  { title: 'Region', summary: (rows, { scope }) => (scope === 'group' ? 'Subtotal' : 'Total') },
  rep:     { title: 'Rep' },
  deals:   { title: 'Deals',   summary: 'sum' },
  revenue: { title: 'Revenue', summary: 'sum' },
};

<wr-table groupBy="region" groupSummary [columns]="columns" [items]="rows" />
Region
Rep
Deals
Revenue
EMEA Alice 12 48200
EMEA Bob 7 21500
Subtotal 19 69700
AMER Cara 15 61000
AMER Diego 9 33750
Subtotal 24 94750
APAC Emi 11 40100
Subtotal 11 40100
Total 54 204550

Custom group header

<ng-template wrTableGroupHeader> replaces the group label. It gets value, count, collapsed, toggle() and more.

<wr-table groupBy="role" [columns]="columns" [items]="rows">
  <ng-template wrTableGroupHeader let-value let-count="count">
    <wr-tag color="primary" transparent>{{ value }}</wr-tag>
    <small>{{ count }} users</small>
  </ng-template>
</wr-table>
Name
Email
Role
admin1 users
Roman [email protected] admin
editor2 users
Alice [email protected] editor
Cara [email protected] editor
viewer2 users
Bob [email protected] viewer
Diego [email protected] viewer

CSV export

exportCsv() downloads the current items, so one page in server mode. toCsv() returns the text; { selectedOnly: true } exports the selection.

<wr-table #table [columns]="columns" [items]="rows" />

<wr-btn (click)="table.exportCsv({ filename: 'users.csv' })">Export CSV</wr-btn>
Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer
Export CSV

Pinned columns

pin: 'left' or pin: 'right' keeps a column in place while the rest scrolls sideways.

const columns: WrTableColumns = {
  name:   { title: 'Name', pin: 'left' },
  email:  { title: 'Email' },
  role:   { title: 'Role' },
  // …more columns in between…
  status: { title: 'Status', pin: 'right' },
};
Name
Email
Role
Department
Location
Joined
Status
Roman [email protected] admin Engineering Almaty 2021-03-12 Active
Alice [email protected] editor Design Berlin 2022-07-01 Active
Bob [email protected] viewer Support Toronto 2023-01-19 Invited
Cara [email protected] editor Marketing São Paulo 2020-11-05 Active
Diego [email protected] viewer Sales Madrid 2024-02-28 Suspended

Resizable columns

resizable: true adds a drag handle to the header; width sets the starting width in px.

const columns: WrTableColumns = {
  name:  { title: 'Name', resizable: true },
  email: { title: 'Email', resizable: true, width: 240 },
  role:  { title: 'Role', resizable: true },
};
Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer

Reorderable columns

With reorderable, headers can be dragged; [(columnOrder)] holds the order. Pinned columns do not move.

<wr-table reorderable [(columnOrder)]="order" [columns]="columns" [items]="rows" />
Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer

Narrow container (stacked cards)

With responsive, rows become labelled cards when the table's own box is narrow.

Name
Email
Role
Roman [email protected] admin
Alice [email protected] editor
Bob [email protected] viewer
Cara [email protected] editor
Diego [email protected] viewer
<div style="width: 360px"><wr-table responsive [columns]="columns" [items]="rows" /></div>

Tree rows

Set childrenKey and items become the roots. rowKey and [(expanded)] work as for expandable rows; treeColumn picks the indented column.

<wr-table
  rowKey="id"
  childrenKey="reports"
  treeColumn="name"
  rowSelection="multiple"
  [columns]="orgColumns"
  [items]="org"
  [(expanded)]="openRows"
  [(selection)]="picked"
/>
Team / person
Reports to
Headcount
Engineering — 0
4
  • The rows take the keyboard: Tab reaches one row, Up and Down move, Right opens a row or steps into it, Left closes it or climbs to the parent, Home and End jump. Enter and Space select the row when rowSelection is set. Tab moves on into the row's own controls. Since v15.
  • pageSize pages the roots. Select all selects only the rows on screen.
  • Tree rows do not combine with groupBy, [wrTableExpand] or virtualScroll.

Types

type WrTableColumns = Record<string, WrTableColumn>;

interface WrTableColumn {
  title: string;
  sortable?: boolean;
  filterItems?: readonly WrTableFilterItem[];
  pin?: 'left' | 'right';
  resizable?: boolean;
  width?: number;
  summary?: WrTableSummary;
  class?: WrClassInput;
}

interface WrTableFilterItem<T = unknown> {
  title: string;
  value: T;
  selected?: boolean;
}

interface WrTableSortState {
  key: string;
  direction: 'asc' | 'desc' | null;
}

interface WrTableCsvOptions {
  filename?: string;
  selectedOnly?: boolean;
  delimiter?: string;
  escapeFormulas?: boolean;
}

interface WrTableGroupContext {
  value: unknown;
  label: string;
  rows: readonly Record<string, unknown>[];
  count: number;
  collapsed: boolean;
  index: number;
  toggle: () => void;
}

type WrTableSummary =
  | 'sum' | 'avg' | 'count' | 'min' | 'max'
  | ((rows: readonly Record<string, unknown>[], context: WrTableSummaryContext) => unknown);

type WrTableSummaryContext =
  | { scope: 'total'; group: null }
  | { scope: 'group'; group: WrTableGroupContext };
NameDescriptionTypeDefault
WrTableColumnsColumn map — keys are row property names.Record<string, WrTableColumn>—
WrTableColumnA single column definition.interface—
titlerequiredHeading shown in the header.string—
sortableShow a clickable sort indicator.booleanfalse
filterItemsNon-empty list shows a filter dropdown.readonly WrTableFilterItem[]—
pinFreeze the column against the 'left' or 'right' edge while the rest scrolls.'left' | 'right'—
resizableAdd a drag handle on the header edge to resize.booleanfalse
widthInitial column width in px (overridden by a drag).number—
summaryFooter aggregate — 'sum' / 'avg' / 'count' / 'min' / 'max', or (rows, context) => value.WrTableSummary—
classExtra classes on this column's header cell and every body cell under it. Not the summary row.WrClassInput—
WrTableFilterItemOne entry in a column filter.interface—
titlerequiredVisible label.string—
valuerequiredValue matched against the cell.T—
selectedPre-check the entry.booleanfalse
WrTableSortStateEmitted by (sortChange).{ key: string; direction: WrTableSortDirection }—
WrTableCsvOptionsArgument of exportCsv() / toCsv().interface—
filenameDownload filename.string'table.csv'
selectedOnlyExport only the selected rows (needs rowSelection).booleanfalse
delimiterField delimiter — use ';' where Excel expects it.string','
escapeFormulasPrefix values starting with = + - @ so spreadsheets keep them as text.booleantrue
WrTableGroupContextPassed to a wrTableGroupHeader template (also the built-in band context).interface—
valueThe value groupBy returned; also the collapse identity.unknown—
labelDefault label — String(value), or '—' for empty.string—
rowsThe group's rows on the current page.readonly Record<string, unknown>[]—
countrows.length — page-scoped row count.number—
collapsedWhether the group is currently collapsed.boolean—
index0-based index of the group on the current page.number—
toggleCollapse / expand this group.() => void—
WrTableSummaryContextA summary function's second argument: which footer row it computes. Since v15.{ scope: 'total'; group: null } | { scope: 'group'; group: WrTableGroupContext }—
scope'total' for the footer row, 'group' for a subtotal under a group.'total' | 'group'—
groupThe group being subtotalled; null for the total.WrTableGroupContext | null—

API

NameDescriptionTypeDefault
columnsrequiredColumn definitions, keyed by row property name.WrTableColumns—
itemsRow items. null/undefined renders the empty state; use [loading] for the spinner. Typed WrTableRow (= object) rather than Record<string, unknown>, so an array of an interface binds as readily as an array of a type — see {@link WrTableRow} for why those two are not the same to TypeScript.readonly WrTableRow[] | null | undefinednull
loadingShow the loading spinner overlay.booleanfalse
responsiveCollapse each row to a labelled card when the table's own box is too narrow for columns (a container query on its own width, not the viewport). Every cell shows its column title as a label.booleanfalse
reorderableEnable drag-to-reorder on the column headers.booleanfalse
columnOrderTwo-way bindable column order — an array of column keys. Reflects and drives the header order: the table falls back to declaration order for any key not listed, and updates this on drag. Bind it to persist a user's arrangement.readonly string[][]
rowSelectionRow selection with a leading checkbox column — 'multiple' adds a select-all header; 'single' keeps one row selected.'single' | 'multiple' | nullnull (off)
rowClassExtra CSS classes per body row, from the row and its drawn index. The <tr> is ngwr's own element and there is no template slot that renders as one, so this is the only way to mark a row — overdue, unsaved, over budget — without a rule that has to re-derive the condition in CSS. It does NOT touch the detail row an expanded row opens, the group header or the summary row: each of those is a sibling <tr> with its own meaning, and a row predicate that painted them too would stripe a table's totals.WrTableRowClass | nullnull
rowKeyHow to identify a row for selection — a property name or a function. Unset uses the row object itself (fine for a stable row array).string | ((row: Record<string, unknown>) => unknown) | nullnull
selectionTwo-way bindable selected row keys.readonly unknown[][]
expandedTwo-way bindable expanded row keys (needs a [wrTableExpand] template).readonly unknown[][]
groupByGroup rows under collapsible band headers — a row property name, or a function returning the group value. null (default) renders the table exactly as before. Grouping runs AFTER pagination: it buckets the rows on the current page in first-appearance order (it never re-sorts your data — same contract as [(sort)]), so a group straddling a page boundary shows a band on both pages, and per-group counts / subtotals are page-scoped. Sort items by the same key upstream to keep groups whole, or pair with pageSize = 0. Group values are compared by identity (Map/Set, SameValueZero) — return a primitive, exactly as for rowKey. Objects / Dates bucket by reference.string | ((row: Record<string, unknown>) => unknown) | nullnull
childrenKeyRender the rows as a hierarchy: names the property (or computes the array) holding each row's children. items then means the ROOTS, and the forest is flattened depth-first into the same <tbody> — child rows are ordinary <tr>s going through the same cell loop, so column pin / resize / drag-reorder and [wrTableCell] templates keep working at every depth. Open state reuses the expanded model and row identity reuses rowKey, so a tree needs no second key space. Everything collapsed is the default. Mutually exclusive with groupBy — a forest has no flat list to bucket, so grouping wins and the hierarchy is ignored while it is set. Also mutually exclusive with [wrTableExpand] detail rows: both own the row's disclosure affordance. pageSize pages the ROOTS; a root brings its open descendants with it. The function form RETURNS WrTableRows, matching [items]: a return is covariant, so an array of interface-typed children satisfies it. Declared as records it did not, and a forest that bound to [items] could not hand its children back out. The PARAMETER stays Record<string, unknown> — that side is contravariant, and widening it would reject the callbacks that compile today while leaving row['reports'] unindexable. See {@link WrTableRow}.string | ((row: Record<string, unknown>) => readonly WrTableRow[] | null | undefined) | nullnull
treeColumnWhich column carries the indent and the expand toggle. Keyed, not positional, so it survives a columnOrder drag. Defaults to whichever column renders first.string | nullnull
toggleRowLabelThe action word that names a parent row's expand toggle. The row's own label cell is joined to it, so the toggle reads "Toggle child rows Engineering" rather than one name shared by every row. Falls back to table.toggleRow.string | nullnull
collapsedGroupsTwo-way bindable collapsed group values (the values groupBy returns). Keyed by value, so a collapsed group stays collapsed across page changes and re-sorts. Empty (the default) shows every group expanded.readonly unknown[][]
groupSummaryRender an aggregate row under each group using the same column.summary definitions as the grand footer, computed over that group's rows on the current page. No-op unless at least one column defines a summary. The grand <tfoot> is unaffected and still aggregates the whole client dataset — with client-side pagination the two describe different scopes, so pair grouping with pageSize = 0 when you need them to reconcile. A summary FUNCTION tells the two apart by its second argument, { scope: 'group' | 'total' } — see WrTableSummaryContext.booleanfalse
showGroupCountShow the page-scoped row-count badge in each group band.booleantrue
sortTwo-way bindable sort array. Order in array = application order.readonly WrTableSortState[][]
emptyLabelText shown when there are no rows. Falls back to table.empty.string | nullnull
sortLabelAccessible name of a column's sort button. Falls back to table.sort.string | nullnull
selectAllLabelAccessible name of the select-all checkbox. Falls back to table.selectAll.string | nullnull
selectRowLabelThe action word that names a row's select checkbox. The row's label cell is joined to it through aria-labelledby — "Select row Ada" — since one name repeated down every row tells a screen reader nothing about which row a box belongs to. Falls back to table.selectRow.string | nullnull
expandRowLabelThe action word that names a row's detail toggle, joined to the row's label cell like selectRowLabel. Falls back to table.expandRow.string | nullnull
selectGroupLabelThe action word that names a group band's select checkbox. The group's label is joined to it — "Select group EMEA". Falls back to table.selectGroup.string | nullnull
toggleGroupLabelThe action word that names a group band's collapse toggle, joined to the group's label like selectGroupLabel. Falls back to table.toggleGroup.string | nullnull
loadingLabelAccessible name of the loading overlay. Falls back to table.loading.string | nullnull
(filterChange)Fires whenever a column's filter selection changes.WrTableFilterChange—
pageSizeRows per page. Set to 0 (default) to disable client-side pagination and render every row at once.number0
pageTwo-way bindable 1-based current page.number1
totalTotal row count for server-side pagination — when set, the table shows the pager but does NOT slice items (you provide the current page's slice yourself and react to (page) changes). Spelled total / page to match <wr-pagination>, which is the control this input renders. The two used to disagree about both words — totalItems / page here against total / currentPage there — which is a real cost for two components designed to sit on one screen. null rather than 0 is the difference that remains, and it carries meaning: it is the OFF switch for server mode, where the pager's own total is a count and 0 means an empty list.number | nullnull
virtualScrollWindow the <tbody> so a table of thousands of rows keeps only ~one viewport of <tr>s in the DOM. Opt-in and OFF by default — a table without it renders byte-identically to today. Engages ONLY on the flat, fixed-height tier: it silently falls back to the full render whenever a variable-height layout is active — groupBy, tree rows (childrenKey), a [wrTableExpand] template, responsive card mode, or a visible pager (pageSize > 0 / total). While on, it forces fixed layout for stable column widths and assumes a uniform row height (see rowHeight). Intended as static config. Give columns an explicit width so the frozen layout is exact (a virtualized table always renders fixed-layout); toggling it off at runtime leaves the frozen widths in place as column minimums.booleanfalse
rowHeightUniform body-row height in px used to map scroll offset to row index. 0 (default) measures the first rendered row once — matching the density at that point — and reuses it; pass an explicit value to remove the post-hydration measure and make SSR pixel-exact (recommended if the density can change at runtime). Read only when virtualScroll is on. Keep virtualized cells single-line and no taller than this — a taller custom [wrTableCell] template drifts the window.number0
viewportHeightHeight of the scroll viewport when virtualScroll is on — a number (px) or any CSS length ('70vh'). Applied as max-height on .wr-table__scroll (a short table shrinks to fit; a long one caps and scrolls). A numeric px value lets the server prerender the exact first window.number | string480
overscanExtra rows kept rendered above and below the viewport as scroll headroom.number6

Templates

NameDescriptionTypeDefault
<ng-template wrTableExpand>Detail template revealed when a row expands (let-row).directive—
<ng-template wrTableGroupHeader>Custom band label template (let-value, let-count, let-toggle, …).directive—
[wrTableCell]Per-column cell template. The attribute value is the column key; import WrTableCell.directive—
let-valueThe cell value, item[columnKey].unknown—
let-row="item"The whole row. A bare let-row binds the cell value instead.Record<string, unknown>—
let-col="column"The column definition.WrTableColumn—

Methods

Call them through a template reference.

NameDescriptionTypeDefault
expandAllRows() / collapseAllRows()Open or close every parent in a tree-mode forest. No-ops when childrenKey is unset.() => void—
exportCsv(options?)Download the rows as a CSV file (options: filename, selectedOnly, delimiter).(WrTableCsvOptions) => void—
toCsv(options?)Return the table as a CSV string instead of downloading.(WrTableCsvOptions) => string—
collapseAllGroups() / expandAllGroups()Collapse or expand every group on the current page.() => void—
scrollToRow(index, behavior?)Scroll a row index to the top of the virtual viewport.(number, ScrollBehavior) => void—

<wr-table-filter>

NameDescriptionTypeDefault
filterLabelAccessible name of the filter trigger. Falls back to table.filter.string | nullnull
columnThe column this filter belongs to, for the trigger's accessible name. Without it every filter trigger in a header row announced the same "Filter column", so a screen-reader user tabbing the row heard the same button three times and could not tell which column each one narrowed. The caret and the funnel sit beside the column title, so sighted readers had the context all along; an aria-label REPLACES content, so nobody else did.string''
noMatchesLabelText shown when the search finds nothing. Falls back to table.noMatches.string | nullnull
searchLabelPlaceholder AND accessible name of the search box. Falls back to table.search. One string for both because the box has no visible label: the placeholder was its only name, so a hard-coded literal left the control unnamed in every other language rather than merely untranslated.string | nullnull
resetLabelLabel of the clear-selection button. Falls back to table.reset.string | nullnull
itemsrequiredThe values the column can be filtered by. Shown as a checkbox list, so pass the distinct values rather than one entry per row.readonly WrTableFilterItem[]—
(selectionChange)Fires whenever the selection changes.readonly WrTableFilterItem[]—

<wr-table-sort>

NameDescriptionTypeDefault
directionWhich way the column is currently sorted, or null for unsorted — the three states the indicator cycles through. Drive it from the table's sort state.WrTableSortDirectionnull

CSS variables

Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/table's own element.

VariableDefaultDeclared on
--wr-table-bgvar(--wr-color-surface).wr-table
--wr-table-bordervar(--wr-border-base).wr-table
--wr-table-card-cell-gapvar(--wr-space-lg).wr-table
--wr-table-card-cell-padding-xvar(--wr-space-lg).wr-table
--wr-table-card-cell-padding-yvar(--wr-space-xs).wr-table
--wr-table-card-row-padding-yvar(--wr-space-sm).wr-table
--wr-table-detail-paddingvar(--wr-space-lg).wr-table
--wr-table-empty-paddingvar(--wr-space-xl).wr-table
--wr-table-expand-btn-paddingvar(--wr-space-xs).wr-table
--wr-table-expand-btn-radiusvar(--wr-border-radius-sm).wr-table
--wr-table-filter-check-bordervar(--wr-border-base).wr-table-filter__panel
--wr-table-filter-check-radiusvar(--wr-border-radius-sm).wr-table-filter__panel
--wr-table-filter-count-font-weightvar(--wr-font-weight-semibold).wr-table-filter
--wr-table-filter-count-padding-xvar(--wr-space-xs).wr-table-filter
--wr-table-filter-count-radiusvar(--wr-border-radius-pill).wr-table-filter
--wr-table-filter-empty-font-sizevar(--wr-text-sm).wr-table-filter__panel
--wr-table-filter-empty-padding-xvar(--wr-space-sm).wr-table-filter__panel
--wr-table-filter-empty-padding-yvar(--wr-space-md).wr-table-filter__panel
--wr-table-filter-item-font-sizevar(--wr-text-sm).wr-table-filter__panel
--wr-table-filter-item-gapvar(--wr-space-sm).wr-table-filter__panel
--wr-table-filter-item-paddingvar(--wr-space-sm).wr-table-filter__panel
--wr-table-filter-item-radiusvar(--wr-border-radius-sm).wr-table-filter__panel
--wr-table-filter-panel-font-familyvar(--wr-font-family-base).wr-table-filter__panel
--wr-table-filter-panel-gapvar(--wr-space-sm).wr-table-filter__panel
--wr-table-filter-panel-paddingvar(--wr-space-sm).wr-table-filter__panel
--wr-table-filter-reset-font-sizevar(--wr-text-sm).wr-table-filter__panel
--wr-table-filter-reset-paddingvar(--wr-space-sm).wr-table-filter__panel
--wr-table-filter-reset-radiusvar(--wr-border-radius-sm).wr-table-filter__panel
--wr-table-filter-search-bordervar(--wr-border-base).wr-table-filter__panel
--wr-table-filter-search-font-sizevar(--wr-text-sm).wr-table-filter__panel
--wr-table-filter-search-paddingvar(--wr-space-sm).wr-table-filter__panel
--wr-table-filter-search-radiusvar(--wr-border-radius-sm).wr-table-filter__panel
--wr-table-filter-trigger-gapvar(--wr-space-xs).wr-table-filter
--wr-table-font-familyvar(--wr-font-family-base).wr-table
--wr-table-font-sizevar(--wr-text-sm).wr-table
--wr-table-foot-cell-font-weightvar(--wr-font-weight-semibold).wr-table
--wr-table-group-bgvar(--wr-color-fill-subtle).wr-table
--wr-table-group-count-font-sizevar(--wr-text-xs).wr-table
--wr-table-group-count-font-weightvar(--wr-font-weight-medium).wr-table
--wr-table-group-count-padding-xvar(--wr-space-sm).wr-table
--wr-table-group-count-radiusvar(--wr-border-radius-pill).wr-table
--wr-table-group-inner-gapvar(--wr-space-sm).wr-table
--wr-table-group-toggle-font-weightvar(--wr-font-weight-semibold).wr-table
--wr-table-group-toggle-gapvar(--wr-space-sm).wr-table
--wr-table-head-bgvar(--wr-color-fill).wr-table
--wr-table-head-font-sizevar(--wr-text-xs).wr-table
--wr-table-head-font-weightvar(--wr-font-weight-semibold).wr-table
--wr-table-head-letter-spacingvar(--wr-tracking-normal).wr-table
--wr-table-head-transformnone.wr-table
--wr-table-line-heightvar(--wr-text-sm-leading).wr-table
--wr-table-pager-paddingvar(--wr-space-md).wr-table
--wr-table-pin-shadow-inklight-dark(rgb(0 0 0 / 18%), rgb(0 0 0 / 45%)).wr-table
--wr-table-radiusvar(--wr-border-radius-base).wr-table
--wr-table-resize-handle-bgvar(--wr-border-base).wr-table
--wr-table-row-hovervar(--wr-color-fill-subtle).wr-table
--wr-table-sort-order-font-sizevar(--wr-table-head-font-size).wr-table
--wr-table-sort-order-font-weightvar(--wr-font-weight-semibold).wr-table
--wr-table-tree-lead-margin-endvar(--wr-space-xs).wr-table +1 variant override
--wr-table-tree-toggle-radiusvar(--wr-border-radius-sm).wr-table

Dependencies

Angular CDKdrag-drop
Built onCheckbox, Dropdown, Pagination, Spinner

See also