CngxChipInteraction
projects/common/interactive/chip-interaction/chip-interaction.directive.ts
Import#
import { CngxChipInteraction } from '@cngx/common/interactive'
Description#
Standalone interactive chip - applies onto <cngx-chip> from
@cngx/common/display and wires role="option" selection
semantics with a local-owned form-bound boolean. Provides
CNGX_CONTROL_VALUE so CngxFormBridge (Phase 7) can adapt it
to Reactive Forms and <cngx-form-field [field]> paths can drive
it through Signal Forms.
Use this when the chip stands alone (e.g. a removable filter
tag, a single suggestion chip outside any chip-group). For chips
inside a <cngx-chip-group> / <cngx-multi-chip-group>, use
[cngxChipInGroup] instead - that variant derives selected
from the parent's selection controller as a computed(),
eliminating the dual-source-of-truth a local model would create.
A dev-mode guard (via afterNextRender) throws if a standalone
chip is mistakenly nested inside a group.
Naming note. The CngxControlValue<boolean> contract requires
a value: ModelSignal<boolean> field; the plan's API surface
uses [(selected)] for the form state and [value] for the
chip's payload. Double aliasing reconciles both: the form-bound
value field is aliased to template binding [(selected)], and
the payload chipValue field is aliased to template binding
[value]. Consumers write
<cngx-chip cngxChipInteraction [value]="x" [(selected)]="b">
unchanged.
A11y. role="option" + reactive aria-selected,
aria-disabled, and tabindex (-1 when disabled, 0 otherwise).
Click + Space + Enter toggle. Backspace + Delete fire
removeRequest - an output, NOT a state mutation. The consumer
decides whether removing the chip means dropping it from a list,
deselecting, or something else.
Close-button click guard. The display chip's own
<button class="cngx-chip__remove"> is a child of the host; its
click event bubbles. handleClick short-circuits when the click
originates from inside that button so the chip's own (click)
toggle does not double-fire alongside the chip's (remove)
output.
Disabled "why". When disabledReason is set, the directive
appends a hidden span to the host via Renderer2 (always-in-DOM
per Pillar 2 - the id stays stable across renders). When
disabledReason is empty, consumers may still pass a custom id
via cngxDescribedBy and the aria-describedby host binding
routes to that. The two paths are mutually exclusive: a non-empty
disabledReason wins.
<cngx-chip cngxChipInteraction [value]="'red'" [(selected)]="redOn">
Red
</cngx-chip>
<span id="chip-locked-reason" hidden>Tag is locked by your role</span>
<cngx-chip
cngxChipInteraction
[value]="tag"
[(selected)]="picked"
[removable]="true"
(removeRequest)="onRemoveTag(tag)"
[disabled]="locked()"
cngxDescribedBy="chip-locked-reason"
>{{ tag }}</cngx-chip>Metadata#
Host#
Providers#
CNGX_CONTROL_VALUE- useExisting
CngxChipInteraction CNGX_FORM_FIELD_CONTROL- useExisting
CngxChipInteraction
Relationships
Index#
Derived State
HostBindings
Inputs#
TChip payload - required identifier the consumer associates with
this chip (e.g. a tag string, filter id, entity reference). The
directive does not inspect or compare it; it is forwarded to
(removeRequest) listeners and visible in the DOM via the
consumer's own bindings. Aliased so consumers write [value]="x"
- see naming note in the class JSDoc.
{ alias: 'value' }string | nullOptional consumer-supplied id of an external description element
(e.g. a sibling sr-only <span>). Consumers bind via
[cngxDescribedBy]="someId". Field name describedBy matches
sibling atoms (CngxChipInGroup, CngxButtonToggle); the alias
preserves the consumer-facing template attribute. Resolved into
aria-describedby via resolvedDescribedBy, which prefers the
internal disabled-reason id when disabledReason is set.
null, { alias: 'cngxDescribedBy', }string | nullOptional id of an external error message element (e.g. a sibling
rendered by <cngx-form-field> or a consumer-owned <span>).
When set, the host emits aria-errormessage="<id>" so AT can
locate the message; consumers MUST render an element with that id
- passing an id without a matching element produces a dangling
AT reference. Default
nullskips the attribute entirely. Note: WAI-ARIA dictates that AT ignores this attribute whenaria-invalidis absent or"false", so a stable always-emitted id is harmless when the field is valid.
nullBridge-writable invalid state. model<boolean> mirrors disabled
so external integrations (RF/Signal-Forms bridges, custom validity
adapters) can drive it without a parallel API path - consumers
typically read only.
falseOutputs#
Bridge-writable invalid state. model<boolean> mirrors disabled
so external integrations (RF/Signal-Forms bridges, custom validity
adapters) can drive it without a parallel API path - consumers
typically read only.
Instance Properties#
Methods#
HostBindings#
| Binding | Expression |
|---|---|
[attr.id] | id() |
[attr.aria-selected] | value() ? "true" : "false" |
[attr.aria-disabled] | disabled() ? "true" : null |
[attr.aria-invalid] | (invalid() || errorState()) ? "true" : null |
[attr.aria-errormessage] | errorMessageId() |
[attr.aria-describedby] | resolvedDescribedBy() |
[attr.tabindex] | disabled() ? -1 : 0 |
[class.cngx-chip-interaction--selected] | value() |
[class.cngx-chip-interaction--disabled] | disabled() |
HostListeners#
| Event | Handler |
|---|---|
(click) | click() |
(keydown.space) | keydown.space() |
(keydown.enter) | keydown.enter() |
(keydown.delete) | keydown.delete() |
(keydown.backspace) | keydown.backspace() |
(focusin) | focusin() |
(focusout) | focusout() |