CngxFilterChips
projects/common/data/filter-chips/filter-chips.component.ts
Import#
import { CngxFilterChips } from '@cngx/common/data'
Description#
Bridge that connects a multi-select chip strip to a CngxFilter
predicate via Pillar-1 derivation - no effect() write-back, no
manual sync. The bridge composes <cngx-multi-chip-group> for
the visible chip strip and registers a single closure-style
predicate on the parent [filterRef]. Chip toggles update
selectedValues; the registered closure reads selectedValues()
lazily on every invocation so downstream filtered consumers
recompute without the bridge ever re-registering.
Filter chips are inherently multi-select per
feedback_select_family_split; a single-mode filter-chip pattern
is intentionally out of scope. Consumers needing a single-select
filter wire <cngx-chip-group> to a custom predicate themselves.
Sync contract - derivation, not effect. The bridge calls
filterRef.addPredicate(filterKey, predicateFn) EXACTLY ONCE
during mount (in afterNextRender, because input.required()
cannot be read from the constructor body). predicateFn is a
closure that reads this.selectedValues() and
this.optionValueFn() on every invocation. Because
CngxFilter.predicate is itself a computed() consumed by
downstream filtered lists, each item-evaluation flows through
selectedValues() in the consumer's reactive context - chip
toggles propagate without any effect() writing back into the
filter.
Empty-selection semantics. When selectedValues() is [],
the closure returns true for every item - equivalent to "no
filter applied". The bridge deliberately does NOT call
removePredicate on empty selection: leaving the predicate
registered with a constant-true short-circuit is cheaper than
re-registering on every empty/non-empty boundary, and keeps the
predicate-stack stable (no predicatesChange emission churn).
Teardown. DestroyRef.onDestroy calls removePredicate only
when registration actually happened (registered flag) - guards
against the case where the component is destroyed before
afterNextRender fires. The destroy callback reads filterRef
and filterKey lazily so a consumer that re-binds these between
mount and unmount cleans the latest binding (per
reference_signal_architecture memory-hygiene rule).
<cngx-filter-chips
label="Tags"
[options]="tagOptions"
optionLabel="label"
optionValue="id"
[filterRef]="employees"
filterKey="tags"
/>
<ng-container cngxFilter #employees="cngxFilter">
@for (emp of filteredEmployees(); track emp.id) {
...
}
</ng-container>Metadata#
Relationships
Index#
Inputs#
Form-state surface forwarded transparently to the inner
<cngx-multi-chip-group>. Each is a model<boolean> matching the
inner shape so consumers can two-way bind with their own forms
state without reaching past the bridge. Mirrors CngxCheckboxGroup
/ CngxButtonToggleGroup precedent.
falseCngxFilter(value: TValue) => unknownMembership key extractor for object-valued chip values. When
TValue is an object (e.g. { id, label } rather than a
primitive id), this fn extracts a stable identity key so chip
membership survives re-emissions with fresh references. Forwarded
to the inner <cngx-multi-chip-group> AND used inside the
predicate's Object.is comparison. Defaults to identity - works
for primitive values.
(v) => v(item: TItem) => stringreadonly TItem[](item: TItem) => TValueTValue[]Internal multi-select state driven by chip toggles. Exposed as
a model so consumers may seed initial selection via
[(selectedValues)] if they need to; usually the consumer leaves
this internal and reads results via the parent
CngxFilter.predicate.
[]Outputs#
Form-state surface forwarded transparently to the inner
<cngx-multi-chip-group>. Each is a model<boolean> matching the
inner shape so consumers can two-way bind with their own forms
state without reaching past the bridge. Mirrors CngxCheckboxGroup
/ CngxButtonToggleGroup precedent.
TValue[]Internal multi-select state driven by chip toggles. Exposed as
a model so consumers may seed initial selection via
[(selectedValues)] if they need to; usually the consumer leaves
this internal and reads results via the parent
CngxFilter.predicate.
Instance Properties#
unknownBuild the slot context for a given chip option. optionValue is
invoked here per option for the chip rendering pass; the predicate
closure invokes it again per LIST item for membership filtering.
Both invocations are necessary because option items (the chip
pool) and filtered list items (the consumer's filtered data) are
separate data paths - they happen to share a shape under the
Phase-5 single-shape limitation, but the bridge cannot memoise
across the two paths without conflating them. Consumers should
keep optionValue cheap (typically a property lookup); a future
[itemValue] input will separate the two extractors.
() => {...}SignalResolved consumer-supplied chip template. The query uses an
instantiation expression (CngxFilterChip<TItem, TValue>) so the
bridge's class generics propagate into the slot directive type;
absent that propagation, contentChild(CngxFilterChip) returns
Signal<CngxFilterChip<unknown, unknown> | undefined> and the
chipContext() helper would have to widen on every call.
contentChild(
CngxFilterChip<TItem, TValue>,
)