https://github.com/h-k-dev/angular-tree
This is a production ready data tree component. The aim is high. My first project that I did not spend just 1 weekend in :D
https://github.com/h-k-dev/angular-tree
Last synced: 6 days ago
JSON representation
This is a production ready data tree component. The aim is high. My first project that I did not spend just 1 weekend in :D
- Host: GitHub
- URL: https://github.com/h-k-dev/angular-tree
- Owner: h-k-dev
- License: mit
- Created: 2026-07-04T18:22:57.000Z (26 days ago)
- Default Branch: main
- Last Pushed: 2026-07-21T10:23:08.000Z (9 days ago)
- Last Synced: 2026-07-21T12:11:31.959Z (9 days ago)
- Language: TypeScript
- Homepage:
- Size: 691 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Roadmap: ROADMAP.md
Awesome Lists containing this project
- awesome-angular - angular-tree - A high-performance, headless tree component that is zoneless, signal-powered, and fully virtualized, with `@angular/cdk` as its sole runtime dependency. (Third Party Components / Data Grids)
- fucking-awesome-angular - angular-tree - A high-performance, headless tree component that is zoneless, signal-powered, and fully virtualized, with `@angular/cdk` as its sole runtime dependency. (Third Party Components / Data Grids)
README
# angular-tree
[](https://github.com/h-k-dev/angular-tree/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@h-k-dev/angular-tree)
[](https://github.com/h-k-dev/angular-tree/blob/main/LICENSE)
High-performance, headless tree component for Angular. Zoneless, signals-only, virtualized from the first row — `@angular/cdk` is the only runtime dependency.
**[▶ Live demo](https://h-k-dev.github.io/angular-tree/)** — 100k-node mode, a drag & drop rules showcase, lazy loading with error + retry, context menus, inline rename, search, dark mode. The demo's full source lives in [`projects/app`](https://github.com/h-k-dev/angular-tree/tree/main/projects/app) and doubles as the reference integration.
## Why angular-tree
- **Virtualized, always** — 100k+ nodes at 60fps (CDK virtual scroll, flat internal model, O(n) recompute). There is no non-virtualized mode: rows are fixed-height (`[itemSize]`) and their DOM is disposable by design — see [Virtualization](https://github.com/h-k-dev/angular-tree/tree/main/docs/VIRTUALIZATION.md) before building stateful row templates
- **Headless** — the tree ships no UI it doesn't own: node content, checkboxes, editors, and menu items are your templates; the tree owns behavior, ARIA, and mechanics
- **Accessor-based** — no forced node shape; describe your data with functions, never reshape it. Async accessors are lazy loading
- **Controlled** — every mutation is an intent (`moved`, `renamed`, …) that you apply to your own state; the tree never touches your data
- **Complete interaction set** — multi-select (Ctrl/Shift/checkbox cascade), drag & drop with per-node rules and copy-on-modifier, keyboard move (Ctrl+X/V), lazy loading with retry, inline rename, type-ahead, search filtering, RTL, built-in context-menu host
- **Accessible** — full APG tree keyboard map, true `aria-setsize`/`aria-posinset` at virtualized edges, roving tabindex or active-descendant, screen-reader announcements — see [Accessibility](https://github.com/h-k-dev/angular-tree/tree/main/docs/ACCESSIBILITY.md)
## Install
```bash
npm install @h-k-dev/angular-tree
```
Peer dependencies: `@angular/core|common|cdk` ≥ 21.2, `rxjs` ≥ 7.8.
## Quick start
```ts
import { Component, signal } from '@angular/core';
import {
AngularTree,
MoveEvent,
TreeNodeDef,
TreeNodeToggle,
} from '@h-k-dev/angular-tree';
interface DocNode {
id: string;
name: string;
children?: DocNode[];
}
@Component({
selector: 'app-docs',
imports: [AngularTree, TreeNodeDef, TreeNodeToggle],
templateUrl: './docs.html',
})
export class Docs {
roots = signal([/* your data */]);
// Accessors DESCRIBE your data — the tree never mutates it.
// Returning a Promise/Observable makes the node lazy.
getChildren = (node: DocNode) => node.children;
getKey = (node: DocNode) => node.id;
isFolder = (node: DocNode) => node.children != null;
// Mutations arrive as INTENTS — apply them to your own state, the tree re-renders.
applyMove({ dragIds, parentId, index }: MoveEvent) {
this.roots.update((roots) => moveNodes(roots, dragIds, parentId, index));
}
}
```
```html
{{ isExpanded ? '▾' : '▸' }}
{{ node.name }}
{{ node.name }}
```
## API at a glance
The most-used surface — the [live demo](https://h-k-dev.github.io/angular-tree/) includes a full API reference page.
| Input | Purpose |
| ----------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `dataSource`, `childrenAccessor`, `expansionKey` | Your data, described by functions; async children = lazy loading |
| `itemSize` | Fixed row height in px — the virtualization contract |
| `selectedKeys`, `multi`, `checkboxSelection` | Controlled selection — `[(selectedKeys)]` two-way over keys; optional cascade |
| `searchTerm`, `searchMatch` | Filtering; matches keep their ancestor chain visible |
| `disableDrag`, `disableDrop`, `disableEdit`, `isSelectable` | Per-node behavior predicates — type rules live in your code, not the tree |
| `rowClass`, `rowStyle` | Per-node row styling accessors; `rowStyle` also reaches the group's guide |
| `childrenDeps` | Declarative lazy-cache invalidation — bind what your accessor reads |
| `expandedKeys`, `defaultExpandedKeys` | Controlled expansion — `[(expandedKeys)]` two-way; or initial-only keys |
| `loading`, `indentGuides` | Root-level loading state, clickable guide lines |
| Output | Fires when |
| --------------------------------------- | -------------------------------------------------------------------- |
| `activated` | Row clicked / Enter — your "open" action |
| `moved` | Drag or keyboard move — `dragIds`, `parentId`, `index`, `dropEffect` |
| `renamed`, `selectionChange`, `toggled` | Inline rename commit, selection set change, expand/collapse |
| `childrenLoaded` | Lazy load resolved or errored (pair with `retryChildren`) |
| `contextRequested` | Right-click / Shift+F10 — feeds the built-in menu host |
## Testing
`@h-k-dev/angular-tree/testing` ships a CDK test harness (`TreeHarness`, `TreeNodeHarness`) including a real drag-gesture simulation (`dragTo`).
## Docs
- [Theming](https://github.com/h-k-dev/angular-tree/tree/main/docs/THEMING.md) — `--tree-*` tokens, Material system-token chain, row state hooks
- [Context menus](https://github.com/h-k-dev/angular-tree/tree/main/docs/CONTEXT-MENUS.md) — built-in host, external menu systems
- [Virtualization](https://github.com/h-k-dev/angular-tree/tree/main/docs/VIRTUALIZATION.md) — sizing, autosize escape hatch
- [Accessibility](https://github.com/h-k-dev/angular-tree/tree/main/docs/ACCESSIBILITY.md) — what the tree guarantees, the one row-template rule, announcements
- [Recipes](https://github.com/h-k-dev/angular-tree/tree/main/docs/RECIPES.md) — `mat-checkbox`, loading masks, dialog refocus
- [Migration](https://github.com/h-k-dev/angular-tree/tree/main/docs/MIGRATION.md) — from PrimeNG `p-tree` / jsTree: accessor adapters, CRUD → intents, synthetic nodes, typed actions
## License
MIT