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

ngx-cfo

v1.6.3

Published

A component library built on top of [AG Grid Community](https://www.ag-grid.com/) and the [Kirby Design System](https://cookbook.kirby.design/), providing a ready-to-use data table with persistent state, row selection, column management, and more.

Readme

ngx-cfo

A component library built on top of AG Grid Community and the Kirby Design System, providing a ready-to-use data table with persistent state, row selection, column management, and more.

Installation

npm install ngx-cfo

AG Grid is bundled as a dependency — no separate ag-grid-community or ag-grid-angular install needed.

Components

<cfo-data-table>

A full-featured data table wrapping AG Grid with built-in search, column visibility/reordering panel, row selection panel, and automatic state persistence (column order, visibility, filters, sort, and quick-filter text are saved to localStorage/sessionStorage per tableId).

Required inputs

| Input | Type | Description | |---|---|---| | tableId | string | Unique ID used to persist table state across sessions | | colDefs | ColDef<T>[] | AG Grid column definitions |

Optional inputs

| Input | Type | Default | Description | |---|---|---|---| | height | string | '500px' | Height of the table. Accepts any valid CSS value ('600px', '100%', 'calc(100vh - 200px)') | | rowData | readonly T[] | [] | Row data (client-side row model) | | rowModelConfig | RowModelConfig | { type: 'clientSide' } | Row model configuration — use { type: 'infinite', datasourceFactory } for server-side data | | getRowId | GetRowIdFunc<T> | — | AG Grid getRowId function | | loading | boolean | false | Shows a loading overlay over the grid | | selection | boolean \| CfoSelectionConfig<T> | — | Enables row selection checkboxes. Pass true for defaults or a config object | | quickFilter | boolean \| CfoQuickFilterConfig | — | Enables the search input. Pass true for defaults or { placeholder } | | overlay | CfoOverlayConfig | — | Customises the no-rows/no-match overlay texts (all default to Danish) | | sidebar | boolean \| CfoSidebarConfig<T> | — | Enables the slide-out sidebar. Pass true for the columns panel, or a config object | | gridOptions | CfoGridOptions<T> | — | Passthrough for any AG Grid options. Merges with the component's own defaults — consumer values take precedence. Only columnDefs, rowData, rowModelType, rowSelection, theme, initialState, overlayComponentSelector, and getRowId are blocked |

Feature matrix

| Feature | How to enable | |---|---| | Kirby card + AG Grid theme | Always on | | Danish no-rows/no-match overlays | Always on (texts customisable via [overlay]) | | State persistence (columns, sort, filter, quick-filter) | Always on (keyed by tableId) | | Opinionated defaultColDef | Always on (overridable via [gridOptions]) | | Segmented control | [segment]="{ items: [...] }" | | Search input | [quickFilter]="true" or [quickFilter]="{ placeholder: 'Search...' }" | | Columns panel + "Visning" button | [sidebar]="true" or [sidebar]="{ columnsPanel: { title: 'Columns' } }" | | Row selection checkboxes | [selection]="true" or [selection]="{ isRowSelectable: (r) => r.active }" | | Selected rows panel | [sidebar]="{ selectedRowsPanel: { rowLabel: (r) => r.name } }" |

CfoSegmentConfig

| Property | Type | Description | |---|---|---| | items | SegmentItem[] | Segment items to display — each has id: string and text: string |

Example:

import type { CfoSegmentConfig, SegmentItem } from 'ngx-cfo';

segments: CfoSegmentConfig = {
  items: [
    { id: 'active',   text: 'Aktive' },
    { id: 'archived', text: 'Arkiverede' },
  ]
};

onSegmentChange(item: SegmentItem) {
  this.rowData = item.id === 'active' ? this.activeRows : this.archivedRows;
}
<cfo-data-table
  [segment]="segments"
  (segmentChange)="onSegmentChange($event)"
  [rowData]="rowData"
  ...
/>

CfoSelectionConfig<T>

| Property | Type | Description | |---|---|---| | isRowSelectable | (row: T) => boolean | Predicate to conditionally disable checkboxes | | tooltipValueGetter | (params: ITooltipParams<T>) => string \| null | Tooltip on checkboxes — pair with isRowSelectable to explain why a row cannot be selected |

CfoSidebarConfig<T>

| Property | Type | Description | |---|---|---| | columnsPanel | CfoColumnsPanelConfig | Enables the column visibility/reorder panel (also shows the "Visning" toolbar button) | | selectedRowsPanel | CfoSelectedRowsPanelConfig<T> | Enables the selected rows panel in the sidebar |

CfoSelectedRowsPanelConfig<T>

| Property | Type | Default | Description | |---|---|---|---| | rowLabel | (row: T) => string | required | Label function for rows shown in the panel | | panelTitle | string | 'Valgte modparter' | Title of the selected rows panel | | transferButtonText | string | 'Overfør til mit overblik' | Text of the transfer button |

Outputs

| Output | Type | Description | |---|---|---| | gridApiReady | GridApi<T> | Fires once when the AG Grid API is ready — use for imperative AG Grid calls (api.exportDataAsCsv(), etc.) | | selectedRowsTransferred | readonly T[] | Fires when the transfer button is clicked, with the currently selected rows | | quickFilterChanged | string | Fires on every quick-filter text change — use for server-side filtering with infinite row model | | segmentChange | SegmentItem | Fires when the user selects a segment — use to swap rowData or colDefs |

Basic usage (client-side)

import { DataTableComponent } from 'ngx-cfo';
import type { ColDef } from 'ngx-cfo';

@Component({
  imports: [DataTableComponent],
  template: `
    <cfo-data-table
      tableId="my-table"
      [colDefs]="colDefs"
      [rowData]="rows"
    />
  `
})
export class MyComponent {
  rows = [{ name: 'Alice', amount: 100 }];
  colDefs: ColDef[] = [{ field: 'name' }, { field: 'amount' }];
}

With search, selection and sidebar

<cfo-data-table
  tableId="my-table"
  [colDefs]="colDefs"
  [rowData]="rows"
  [quickFilter]="true"
  [selection]="{ isRowSelectable: canSelect }"
  [sidebar]="{
    columnsPanel: {},
    selectedRowsPanel: { rowLabel: rowLabel }
  }"
  (selectedRowsTransferred)="onTransfer($event)"
/>

gridOptions examples

// Enable pagination
[gridOptions]="{ pagination: true, paginationPageSize: 25 }"

// Override default column settings
[gridOptions]="{ defaultColDef: { sortable: false, minWidth: 100 } }"

// Enable master/detail
[gridOptions]="{ masterDetail: true, detailCellRendererParams: { ... } }"

Infinite (server-side) row model

import { DataTableComponent } from 'ngx-cfo';
import type { InfiniteDatasourceFactory } from 'ngx-cfo';

datasourceFactory: InfiniteDatasourceFactory = (quickFilterText) => ({
  getRows: (params) => {
    myApi.fetchRows({ filter: quickFilterText, ...params }).then(({ rows, total }) => {
      params.successCallback(rows, total);
    });
  }
});

rowModelConfig = { type: 'infinite' as const, datasourceFactory: this.datasourceFactory };
<cfo-data-table
  tableId="server-table"
  [colDefs]="colDefs"
  [rowModelConfig]="rowModelConfig"
  [quickFilter]="true"
  (quickFilterChanged)="onFilter($event)"
/>

Height

AG Grid requires a defined height to render. Set it via the height input:

<!-- Fixed height (default: 500px) -->
<cfo-data-table height="600px" ... />

<!-- Fill remaining viewport -->
<cfo-data-table height="calc(100vh - 200px)" ... />

<!-- Inside a flex parent -->
<div style="display: flex; flex-direction: column; height: 100vh">
  <header style="height: 60px">...</header>
  <cfo-data-table style="flex: 1; min-height: 0" height="100%" ... />
</div>

<cfo-page-shell>

A page layout component with tab navigation.

| Input | Type | Default | Description | |---|---|---|---| | tabs | { label: string; path: string }[] | required | Tab items to display | | title | string | — | Optional page title | | selectedTabIndex | number | 0 | Initially selected tab index |

<cfo-page-shell title="My Page" [tabs]="[{ label: 'Overview', path: 'overview' }]">
  <router-outlet />
</cfo-page-shell>

<cfo-slide-out-panel>

A slide-out side panel, animating in from the right.

| Input | Type | Default | Description | |---|---|---|---| | open | boolean | false | Whether the panel is open | | width | string | '20rem' | CSS width of the panel |

<cfo-slide-out-panel [open]="isOpen">
  <p>Panel content</p>
</cfo-slide-out-panel>

Types

Component config types

import type {
  CfoGridOptions,
  CfoSelectionConfig,
  CfoQuickFilterConfig,
  CfoOverlayConfig,
  CfoSidebarConfig,
  CfoColumnsPanelConfig,
  CfoSelectedRowsPanelConfig,
  CfoRowMenuItem,
  CfoRowMenuParams,
  CfoMessagesFlagParams,
  CfoMessage,
  CfoAmountWithProgressParams,
  CfoSegmentConfig,
  SegmentItem,
} from 'ngx-cfo';

AG Grid types

All commonly needed AG Grid types are re-exported from ngx-cfo — no need to import from ag-grid-community directly:

import type {
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridState,
  ICellRendererParams,
  IDatasource,
  IRowNode,
  ITooltipParams,
  RowSelectedEvent,
  SelectionChangedEvent,
  ValueFormatterParams,
  ValueGetterParams,
} from 'ngx-cfo';

<cfo-data-table>

A full-featured data table wrapping AG Grid with built-in search, column visibility/reordering panel, row selection panel, and automatic state persistence (column order, visibility, filters, sort, and quick-filter text are saved to localStorage/sessionStorage per tableId).

Required inputs

| Input | Type | Description | |---|---|---| | tableId | string | Unique ID used to persist table state across sessions | | colDefs | ColDef<T>[] | AG Grid column definitions | | selectedRowLabel | (row: T) => string | Label function for rows shown in the selected rows panel |

Optional inputs

| Input | Type | Default | Description | |---|---|---|---| | rowData | readonly T[] | [] | Row data (client-side row model) | | rowModelConfig | RowModelConfig | { type: 'clientSide' } | Row model configuration — use { type: 'infinite', datasourceFactory } for server-side data | | getRowId | GetRowIdFunc<T> | — | AG Grid getRowId function | | loading | boolean | false | Shows a loading overlay over the grid | | quickFilterPlaceholder | string | 'Søg' | Placeholder text for the search input | | columnPanelTitle | string | 'Kolonner' | Title of the column visibility/reorder panel | | selectedRowsPanelTitle | string | 'Valgte modparter' | Title of the selected rows panel | | selectedRowsTransferButtonText | string | 'Overfør til mit overblik' | Text of the transfer button in the selected rows panel | | noRowsOverlayTitle | string | 'Ingen data i tabellen' | Title shown when there are no rows | | noRowsOverlaySubtitle | string | 'Der er ingen rækker at vise endnu.' | Subtitle shown when there are no rows | | noMatchingRowsOverlayTitle | string | 'Ingen matchende rækker' | Title shown when no rows match the filter | | noMatchingRowsOverlaySubtitle | string | 'Prøv at justere din søgning eller dine filtre.' | Subtitle shown when no rows match the filter | | isRowSelectable | (row: T) => boolean | — | Predicate to conditionally disable row selection checkboxes | | selectionTooltipValueGetter | (params: ITooltipParams<T>) => string \| null | — | Tooltip text for selection checkboxes — pair with isRowSelectable to explain why a row cannot be selected | | gridOptions | Omit<GridOptions<T>, ...> | — | Passthrough for any AG Grid options not covered by the component's own inputs |

Outputs

| Output | Type | Description | |---|---|---| | gridApiReady | GridApi<T> | Fires once when the AG Grid API is ready | | selectedRowsTransferred | readonly T[] | Fires when the transfer button is clicked, with the currently selected rows | | quickFilterChanged | string | Fires on every quick-filter text change — use this for server-side filtering with infinite row model |

Basic usage (client-side)

import { DataTableComponent } from 'ngx-cfo';
import { ColDef } from 'ag-grid-community';

@Component({
  imports: [DataTableComponent],
  template: `
    <cfo-data-table
      tableId="my-table"
      [colDefs]="colDefs"
      [rowData]="rows"
      [selectedRowLabel]="rowLabel"
    />
  `
})
export class MyComponent {
  rows = [{ name: 'Alice', amount: 100 }];
  colDefs: ColDef[] = [{ field: 'name' }, { field: 'amount' }];
  rowLabel = (row: typeof this.rows[0]) => row.name;
}

Infinite (server-side) row model

import { DataTableComponent, InfiniteDatasourceFactory } from 'ngx-cfo';

datasourceFactory: InfiniteDatasourceFactory = (quickFilterText) => ({
  getRows: (params) => {
    myApi.fetchRows({ filter: quickFilterText, ...params }).then(({ rows, total }) => {
      params.successCallback(rows, total);
    });
  }
});

rowModelConfig = { type: 'infinite' as const, datasourceFactory: this.datasourceFactory };
<cfo-data-table
  tableId="server-table"
  [colDefs]="colDefs"
  [rowModelConfig]="rowModelConfig"
  [selectedRowLabel]="rowLabel"
  (quickFilterChanged)="onFilter($event)"
/>

<cfo-page-shell>

A page layout component with tab navigation.

| Input | Type | Default | Description | |---|---|---|---| | tabs | { label: string; path: string }[] | required | Tab items to display | | title | string | — | Optional page title | | selectedTabIndex | number | 0 | Initially selected tab index |

<cfo-page-shell title="My Page" [tabs]="[{ label: 'Overview', path: 'overview' }]">
  <router-outlet />
</cfo-page-shell>

<cfo-slide-out-panel>

A slide-out side panel, animating in from the right.

| Input | Type | Default | Description | |---|---|---|---| | open | boolean | false | Whether the panel is open | | width | string | '20rem' | CSS width of the panel |

<cfo-slide-out-panel [open]="isOpen">
  <p>Panel content</p>
</cfo-slide-out-panel>

Cell renderers

These can be used directly in AG Grid colDef.cellRenderer.

ActionCellRendererComponent

Renders a Kirby icon button for row actions (e.g. opening a context menu).

import { ActionCellRendererComponent } from 'ngx-cfo';

colDefs: ColDef[] = [
  { headerName: '', cellRenderer: ActionCellRendererComponent, width: 56 }
];

ProgressBarCellRendererComponent

Renders a color-coded progress bar (green < 80%, yellow 80–99%, red ≥ 100%). Expects a numeric cell value (0–100+). Shows a loading state for null/undefined and an error state for NaN.

import { ProgressBarCellRendererComponent } from 'ngx-cfo';

{ field: 'utilization', cellRenderer: ProgressBarCellRendererComponent }

emptyTextRenderer

A function renderer that shows a greyed-out italic "Ingen data" placeholder for empty/null values.

import { emptyTextRenderer } from 'ngx-cfo';

{ field: 'note', cellRenderer: emptyTextRenderer }

CfoRowMenuCellRendererComponent

Renders a "more" icon button () that opens a Kirby dropdown menu for per-row actions. Supports clickable actions, router-link navigation items, dividers, and labelled sections. Items can be conditionally disabled or hidden per row.

import { CfoRowMenuCellRendererComponent } from 'ngx-cfo';
import type { CfoRowMenuParams } from 'ngx-cfo';

colDefs: ColDef<Customer>[] = [
  { field: 'name' },
  {
    headerName: '',
    colId: 'row-menu',
    pinned: 'right',
    lockPinned: true,
    width: 56,
    resizable: false,
    sortable: false,
    cellRenderer: CfoRowMenuCellRendererComponent,
    cellRendererParams: {
      items: (row) => [
        { label: 'Rediger', icon: 'edit', click: (r) => this.edit(r) },
        { type: 'link', label: 'Se detaljer', icon: 'list', routerLink: (r) => ['/customers', r.id] },
        { type: 'divider' },
        {
          type: 'section', label: 'Avanceret',
          items: [
            {
              label: 'Slet', icon: 'trash', click: (r) => this.delete(r),
              disabled: (r) => !r.canDelete,
              description: 'Kræver admin-rettighed',
            },
          ],
        },
      ]
    } satisfies CfoRowMenuParams<Customer>
  }
];

CfoRowMenuItem<TData> union

| Type | Required fields | Description | |---|---|---| | 'action' (default) | label, click | Clickable item that invokes a function | | 'link' | label, routerLink | Angular router navigation | | 'divider' | — | Visual separator | | 'section' | label, items | Labelled group header followed by its items | | 'info' | label | Non-interactive read-only item — use for metadata like "Edited by" |

All non-divider items support:

| Field | Type | Description | |---|---|---| | icon | string | Kirby icon name shown at the start | | disabled | boolean \| ((row: T) => boolean) | Disables the item ('action' and 'link' only) | | description | string \| ((row: T) => string) | Help text shown as a subtitle below the label | | hidden | boolean \| ((row: T) => boolean) | Hides the item entirely |

Link items also accept:

| Field | Type | Description | |---|---|---| | queryParams | (row: T) => Record<string, unknown> | Optional query params factory |

Example with 'info':

{ type: 'divider' },
{ type: 'info', label: 'Sidst redigeret af', icon: 'person', description: (r) => r.editedBy }

CfoMessagesFlagCellRendererComponent

Renders a text value with an optional kirby-flag badge showing the count of messages on the row. The flag color reflects the worst severity across all messages.

| Severity | Flag color | Notes | |---|---|---| | error | danger (red) | White text override applied automatically | | warning | warning (yellow) | | | info | semi-light (grey) | | | success | success (green) | |

When there are no messages the flag is not rendered.

import { CfoMessagesFlagCellRendererComponent } from 'ngx-cfo';
import type { CfoMessagesFlagParams, CfoMessage } from 'ngx-cfo';

{
  field: 'selskabNavn',
  cellRenderer: CfoMessagesFlagCellRendererComponent,
  cellRendererParams: {
    messagesGetter: (row: MyRow) => row.messages,
  } satisfies CfoMessagesFlagParams<MyRow>
}

CfoMessagesFlagParams<T>

| Property | Type | Description | |---|---|---| | messagesGetter | (row: T) => CfoMessage[] | Returns the messages for a row |

CfoMessage

| Property | Type | Description | |---|---|---| | severity | 'success' \| 'info' \| 'warning' \| 'error' | Severity level — determines flag color | | title | string | Message text |


CfoAmountWithProgressCellRendererComponent

Renders a formatted amount with an optional thin progress bar above it. Different from ProgressBarCellRendererComponent — the cell value is the amount (not a percentage), and the percentage comes from a separate field via percentageGetter.

┌──────────────────────┐
│  ████████░░░░░░░░░░  │  ← progress bar (hidden when percentageGetter returns undefined)
│      1.234.567 kr.   │  ← right-aligned, formatted by formatter()
└──────────────────────┘
import { CfoAmountWithProgressCellRendererComponent } from 'ngx-cfo';
import type { CfoAmountWithProgressParams } from 'ngx-cfo';

{
  field: 'restgaeld',
  cellRenderer: CfoAmountWithProgressCellRendererComponent,
  cellRendererParams: {
    percentageGetter: (row: MyRow) => row.restgaeldPct,
    formatter: (value) => value == null ? '' : `${value.toLocaleString('da-DK')} kr.`,
  } satisfies CfoAmountWithProgressParams<MyRow>
}

CfoAmountWithProgressParams<T>

| Property | Type | Description | |---|---|---| | percentageGetter | (row: T) => number \| undefined | Returns fill % (0–100). Return undefined to hide the bar entirely | | formatter | (value: number \| null) => string | Formats the cell value into a display string |

Running end-to-end tests

For end-to-end (e2e) testing, run:

ng e2e

Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.

Additional Resources

For more information on using the Angular CLI, including detailed command references, visit the Angular CLI Overview and Command Reference page.