CngxTreeController
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 acomputed()or pure fn. isExpanded(id)returns the SAMESignalinstance per id across the controller's lifetime - safe to pass into OnPush children without churn.- Controller is a11y-agnostic - adapters like
createTreeAdItemslive 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 incomputed()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 aneffect()that should NOT re-fire on tree changes, wrap the call inuntracked(() => 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#
Methods#
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).
TT[]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.
TRelease the isExpanded(id) signal-cache. Soft contract - matches
createSelectionController's destroy semantics:
- New
isExpanded(id)queries return a sharedSignal<false>constant. - Existing signal bindings created before destroy keep working - the
underlying
expandedIdssignal 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.
FlatTreeNode | undefinedfindByValue(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().
T