Skip to main content
cngx-src documentation

CngxFilterChips

ComponentPrimaryOnPushv0.1.0WCAG AA

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. predicateFn is a closure that reads this.selectedValues() and this.optionValue() 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. Registration itself is reactive: a binding effect tracks filterRef/filterKey (a rebind moves the registration), and an eviction watcher re-registers the key after filter.clear() wiped the stack, so visible chip selection and predicate stack cannot diverge. Selections whose option vanished from [options] are pruned - a removed option never keeps filtering invisibly.

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. The binding effect's cleanup removes the key only while it still maps to this strip's own predicateFn (identity guard) - destroying one of two strips that share a key never detaches the live sibling's registration.

Visible caption. The required label renders as a .cngx-filter-chips__label caption above the strip so a sighted user can tell which facet a chip row belongs to. The caption is aria-hidden="true": the inner <cngx-multi-chip-group> is already named by the same string via [label], so exposing the visible node to AT would announce the facet twice. Spacing between caption and strip is the --cngx-filter-chips-gap token; the caption's own appearance is --cngx-filter-chips-label-color, --cngx-filter-chips-label-font-size and --cngx-filter-chips-label-font-weight.

<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>

https://cngxjs.github.io/cngx/examples/#/common/data/filter-chips/custom-chip-decoration-via-cngxfilterchip https://cngxjs.github.io/cngx/examples/#/common/data/filter-chips/multi-role-filter-wired-to-a-list

Metadata#

Content Slots#

Host#

Relationships

Index#

Inputs#

disabled#boolean
model()

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.

default false
errorMessageId#string | null
input()
default null
filterKey#string
input()Required
filterRef#CngxFilter
Required
invalid#boolean
model()
default false
keyFn#(value: TValue) => unknown

Membership 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.

default (v) => v
label#string
input()Required
optionLabel#(item: TItem) => string
Required
options#readonly TItem[]
input()Required
optionValue#(item: TItem) => TValue
Required
orientation#"horizontal" | "vertical"
input()
default 'horizontal'
required#boolean
model()
default false
selectedValues#TValue[]
model()

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.

default []

Outputs#

disabled#boolean
model()

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.

invalid#boolean
model()
required#boolean
model()
selectedValues#TValue[]
model()

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#

chipContext#unknown
ProtectedReadonly

Build 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.

() => {...}
chipTemplate#Signal
contentChild()ProtectedReadonly

Resolved 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>,
)

HostBindings#

BindingExpression
[class.cngx-filter-chips--vertical]orientation() === "vertical"