{"id":50275511,"url":"https://github.com/raknjarasoa/ngx-powerful-tree","last_synced_at":"2026-05-27T20:02:31.791Z","repository":{"id":359889354,"uuid":"1247892535","full_name":"raknjarasoa/ngx-powerful-tree","owner":"raknjarasoa","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-24T00:09:17.000Z","size":273,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T01:20:00.198Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/raknjarasoa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-23T23:24:28.000Z","updated_at":"2026-05-24T00:09:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/raknjarasoa/ngx-powerful-tree","commit_stats":null,"previous_names":["raknjarasoa/ngx-powerful-tree"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/raknjarasoa/ngx-powerful-tree","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raknjarasoa%2Fngx-powerful-tree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raknjarasoa%2Fngx-powerful-tree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raknjarasoa%2Fngx-powerful-tree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raknjarasoa%2Fngx-powerful-tree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/raknjarasoa","download_url":"https://codeload.github.com/raknjarasoa/ngx-powerful-tree/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raknjarasoa%2Fngx-powerful-tree/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33581559,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-27T20:02:30.965Z","updated_at":"2026-05-27T20:02:31.782Z","avatar_url":"https://github.com/raknjarasoa.png","language":"TypeScript","funding_links":[],"categories":["Third Party Components"],"sub_categories":["Data Grids"],"readme":"# ngx-powerful-tree\n\nA virtualized Angular tree component with native HTML5 drag-and-drop, fluid\nsearch, locked subtrees, and folder/file picker modes. Built on\n`@angular/cdk/scrolling`. Designed to stay smooth at\n100k+ rows.\n\n## Installation\n\n```sh\nnpm i ngx-powerful-tree @angular/cdk\n```\n\n## Quick start\n\n```ts\nimport { Component, signal, viewChild } from '@angular/core';\nimport { NgxPowerfulTree, NgxTreeNode } from 'ngx-powerful-tree';\n\n@Component({\n  imports: [NgxPowerfulTree],\n  template: `\n    \u003cngx-powerful-tree\n      #tree\n      [nodes]=\"nodes()\"\n      [searchQuery]=\"search()\"\n      (itemMoved)=\"onMoved($event)\"\n      (itemRenamed)=\"onRenamed($event)\"\n      (itemDeleted)=\"onDeleted($event)\"\n    /\u003e\n  `,\n})\nexport class MyTree {\n  tree = viewChild.required\u003cNgxPowerfulTree\u003e('tree');\n\n  nodes = signal\u003cNgxTreeNode[]\u003e([\n    {\n      id: 'src',\n      name: 'src',\n      isFolder: true,\n      children: [{ id: 'app.ts', name: 'app.ts', isFolder: false }],\n    },\n  ]);\n  search = signal\u003cstring\u003e('');\n\n  async refresh() {\n    const data = await this.api.fetchTree();\n    this.tree().reload(data);\n  }\n}\n```\n\n## State ownership: the contract\n\n**The tree owns its state after the first input emission.** `nodes` is read\nonce on mount and then ignored — internal mutations (move/rename/add/delete\nvia the UI or store) survive parent re-emissions without being silently\noverwritten.\n\nTo swap the dataset entirely (e.g. after loading from a server), call the\npublic `reload()` method. It accepts the same nested `NgxTreeNode[]` shape\nas the `nodes` input and clears expand/select/focus/search/drag state so\nthe new dataset starts from a clean slate.\n\nTo keep an external mirror in sync, subscribe to the fine-grained outputs\n(`itemMoved`, `itemRenamed`, `itemAdded`, `itemDeleted`,\n`selectionChanged`, `focusedChanged`). The tree never emits a full-tree\nsnapshot — at 100k+ items that would dwarf the cost of the mutation itself.\n\n## Inputs\n\n| Input              | Type                            | Default   | Description                                                                            |\n| ------------------ | ------------------------------- | --------- | -------------------------------------------------------------------------------------- |\n| `nodes` (required) | `NgxTreeNode[]`                 | —         | Seed dataset. Read once on first emission; call `reload(nodes)` to swap it afterwards. |\n| `searchQuery`      | `string`                        | `''`      | Substring filter. Debounced by `searchDebounceMs`.                                     |\n| `searchDebounceMs` | `number`                        | `120`     | Debounce window for search input. `0` to apply immediately.                            |\n| `multiSelect`      | `boolean`                       | `false`   | Allow selecting multiple items with click + meta or Space.                             |\n| `itemSize`         | `number`                        | `40`      | Row height in pixels for `CdkVirtualScrollViewport`.                                   |\n| `selectableTypes`  | `'files' \\| 'folders' \\| 'all'` | `'files'` | Which item kinds can be selected. Use `'folders'` for a folder picker.                 |\n| `readOnly`         | `boolean`                       | `false`   | Disable drag/rename/delete/add UI.                                                     |\n| `actions`          | `NgxTreeActions`                | `{}`      | Per-action availability. See below.                                                    |\n\n### `actions` input\n\n`NgxTreeActions` has four optional keys — `add`, `rename`, `delete`, `move`.\nEach value can be a `boolean` or a per-row predicate\n`(item: NgxTreeProxyItem) =\u003e boolean`. **Omitted keys default to `true`.**\n\n```html\n\u003c!-- Disable delete globally; keep add/rename/move --\u003e\n\u003cngx-powerful-tree [nodes]=\"nodes()\" [actions]=\"{ delete: false }\" /\u003e\n\n\u003c!-- Disable delete only for folders that still have children --\u003e\n\u003cngx-powerful-tree [nodes]=\"nodes()\" [actions]=\"{ delete: deleteWhenEmpty }\" /\u003e\n```\n\n```ts\ndeleteWhenEmpty = (item: NgxTreeProxyItem) =\u003e !item.isFolder || item.children.length === 0;\n```\n\n### Truncate vs wrap\n\nNames are truncated with an ellipsis by default. To let names wrap onto\nmultiple lines, add the `ngx-tree-wrap` class on the host:\n\n```html\n\u003cngx-powerful-tree [nodes]=\"nodes()\" class=\"ngx-tree-wrap\" /\u003e\n```\n\n## Headless Theming (CSS Variables)\n\nThe tree provides a clean, wireframed design that delegates all colors, spacings, and borders to CSS variables. You can easily override these variables on the component host to integrate the tree seamlessly with your application's design system:\n\n```css\n.tree-wrapper ngx-powerful-tree {\n  /* Demonstrate headless design overrides - customize to match playground container theme */\n  --ngx-tree-background: var(--pg-surface);\n  --ngx-tree-text-color: var(--pg-color);\n  --ngx-tree-selection-bg: rgba(59, 130, 246, 0.15);\n  --ngx-tree-row-hover-bg: rgba(255, 255, 255, 0.03);\n  --ngx-tree-focus-ring: var(--pg-accent-blue);\n  --ngx-tree-drag-line: var(--pg-accent-blue);\n  --ngx-tree-container-border: var(--pg-border);\n  --ngx-tree-container-border-radius: 8px;\n  --ngx-tree-font-size-base: 0.92rem;\n  --ngx-tree-row-height-min: 38px;\n  --ngx-tree-row-padding-base: 5px 12px;\n}\n```\n\n## Outputs\n\n| Output             | Payload                                                          |\n| ------------------ | ---------------------------------------------------------------- |\n| `itemMoved`        | `{ draggedId, targetId, position: 'before'\\|'after'\\|'inside' }` |\n| `itemRenamed`      | `{ id, name }`                                                   |\n| `itemAdded`        | `{ parentId, node }`                                             |\n| `itemDeleted`      | `id`                                                             |\n| `selectionChanged` | `string[]` (sorted ids)                                          |\n| `focusedChanged`   | `string \\| null`                                                 |\n| `moveRequested`    | `id` (consumer opens a relocation picker)                        |\n\n`selectionChanged` and `focusedChanged` emit raw — they fire on every\nunderlying state change, even when the payload is identical. Dedupe in the\nconsumer if you need to.\n\n## Locked subtrees\n\nSet `locked: true` on any node to make it and its descendants read-only.\nThe lock is enforced by the store: `addItem`, `deleteItem`, `renameItem`,\n`moveItem`, and `setEditingItemId` reject operations on locked nodes and\nreturn `false`. Lock state propagates down at runtime — child items\ninherit the lock through `parentMap` traversal, you don't need to set\n`locked` on every descendant.\n\n## Custom templates\n\nProject `\u003cng-template #itemTemplate\u003e` to override every row, or\n`\u003cng-template #fileTemplate\u003e` to override only files. Both are looked up\nreactively, so wrapping them in `@if` blocks for conditional rendering is\nsupported:\n\n```html\n\u003cngx-powerful-tree [nodes]=\"nodes()\"\u003e\n  @if (useCustomFileTemplate()) {\n  \u003cng-template #fileTemplate let-item\u003e\n    \u003c!-- your custom file row --\u003e\n  \u003c/ng-template\u003e\n  }\n\u003c/ngx-powerful-tree\u003e\n```\n\n### Aligning custom rows with default rows\n\nThe default folder row starts with a 26px-wide chevron button (or\nplaceholder when the folder has no visible children). If you write a custom\n`#fileTemplate`, files won't have that chevron — to make custom files align\nhorizontally with sibling folders, reserve the same 26px of leading space\nyourself:\n\n```html\n\u003cng-template #fileTemplate let-item\u003e\n  \u003cdiv class=\"ngx-tree-row-content\"\u003e\n    \u003c!-- Offset expand button space (26px) so custom files align perfectly with folders --\u003e\n    \u003cdiv style=\"width: 26px; flex-shrink: 0\" aria-hidden=\"true\"\u003e\u003c/div\u003e\n\n    \u003cspan class=\"ngx-tree-item-icon\"\u003e\u003ci class=\"fa-solid fa-file\"\u003e\u003c/i\u003e\u003c/span\u003e\n    \u003cspan class=\"ngx-tree-item-name\"\u003e{{ item.name }}\u003c/span\u003e\n  \u003c/div\u003e\n\u003c/ng-template\u003e\n```\n\n## Running unit tests\n\n```sh\nnx test ngx-powerful-tree\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraknjarasoa%2Fngx-powerful-tree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraknjarasoa%2Fngx-powerful-tree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraknjarasoa%2Fngx-powerful-tree/lists"}