Skip to main content
cngx-src documentation

CngxTreeController

Interface

projects/common/interactive/tree-controller/tree-controller.ts

Referenced by#

Import#

import { CngxTreeController } from '@cngx/common/interactive'

Description#

Signal-native tree controller. Produces flat-projected views of a hierarchical source, owns the expansion-set, and exposes value- and id-based lookups used by the surrounding render / keyboard-nav layers.

Derivation contract

  • No effect(), no subscriptions. The only writable slot is the internal expandedIds set; every other accessor is a computed() or pure fn.
  • isExpanded(id) returns the SAME Signal instance per id across the controller's lifetime - safe to pass into OnPush children without churn.
  • Controller is a11y-agnostic - adapters like createTreeAdItems live in sibling files so @cngx/core-level reuse stays clean.

Reactivity contract

The surface splits into two classes of accessors, by design:

  • Reactive (Signal<…> returners): flatNodes, visibleNodes, expandedIds, isExpanded(id). Bind these in templates and wrap in computed() freely - Angular tracks their dependencies automatically.

  • Snapshot (plain-return methods): findById, parentOf, firstChildOf, childrenOfValue, descendantsOfValue. These read the underlying indexes reactively when called inside a tracked context, but intentionally return raw values rather than signals. They are intended for imperative call-sites - keydown handlers, commit flows, AD-activation dispatch - where the caller wants a one-shot lookup, not a subscription. If you do call them from inside an effect() that should NOT re-fire on tree changes, wrap the call in untracked(() => ctrl.parentOf(id)).

Mutations

All mutators (expand, collapse, toggle, expandAll, collapseAll) peek at the expansion set via untracked() so invoking them from inside an effect() never latches that effect onto the expansion set.

Index#

Instance Properties#

expandedIds#Signal>
Readonly

Read-only view of the expansion set.

flatNodes#Signal[]>
Readonly

Flat DFS projection with ARIA metadata + backref to the source node.

visibleNodes#Signal[]>
Readonly

Flat nodes minus those under a collapsed ancestor.

Methods#

childrenOfValue#T[]
childrenOfValue(value: T)

Direct-children values of a node, for selection childrenFn.

Returns a fresh array per call. SelectionController's cascade isIndeterminate walk invokes childrenFn once per tree node on every membership change - a cascade-toggle on a 10k-descendant root triggers O(n) fresh allocations per recompute. Tracked as a perf follow-up: memoize by (tree-version, value-key) if the 10k demo surfaces measurable jitter; spec tree-controller.spec.ts carries a reserved baseline-benchmark slot (it.todo).

@paramvalueT
collapse#void
collapse(id: string)
@paramidstring
collapseAll#void
descendantsOfValue#T[]
descendantsOfValue(value: T)

All descendant values (DFS, exclusive of self), for cascade-select. Same allocation caveat as childrenOfValue - returns a fresh array; cascade-select on wide subtrees allocates O(descendants) per call.

@paramvalueT
destroy#void

Release the isExpanded(id) signal-cache. Soft contract - matches createSelectionController's destroy semantics:

  • New isExpanded(id) queries return a shared Signal<false> constant.
  • Existing signal bindings created before destroy keep working - the underlying expandedIds signal is still live, so downstream updates continue to propagate.
  • Mutators (expand / collapse / toggle / expandAll / collapseAll) continue to function; post-destroy writes do NOT repopulate the cache.

Idempotent. Prefer this over a hard teardown so long-lived consumer bindings that outlive the controller's active phase degrade gracefully rather than throwing.

expand#void
expand(id: string)
@paramidstring
expandAll#void

Expand every node that has children. No-op when already fully expanded.

findById#FlatTreeNode | undefined
findById(id: string)
@paramidstring
findByValue#FlatTreeNode | undefined
findByValue(value: T)

O(1) lookup of the flat projection for a value, using the same keyFn that backs selection membership. Returns undefined when the value is not in the current tree (stale selection, swapped source, etc.). Prefer this over a linear scan of flatNodes().

@paramvalueT
firstChildOf#FlatTreeNode | undefined
firstChildOf(id: string)
@paramidstring
isExpanded#Signal
isExpanded(id: string)

Stable-identity membership signal per id.

@paramidstring
parentOf#FlatTreeNode | undefined
parentOf(id: string)
@paramidstring
toggle#void
toggle(id: string)
@paramidstring