Skip to main content
cngx-src documentation

CngxChipInteraction

DirectivePrimaryv0.1.0WCAG AA

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>

https://cngxjs.github.io/cngx/examples/#/common/interactive/chip/interaction/basic-toggle-on-click-space-or-enter https://cngxjs.github.io/cngx/examples/#/common/interactive/chip/interaction/disabled-state https://cngxjs.github.io/cngx/examples/#/common/interactive/chip/interaction/removable-with-removerequest-on-backspace-delete https://cngxjs.github.io/cngx/examples/#/forms/field/form-primitives/coming-in-a-follow-up https://cngxjs.github.io/cngx/examples/#/forms/field/form-primitives/reactive-forms-same-atom-just-bind-formcontrol https://cngxjs.github.io/cngx/examples/#/forms/field/form-primitives/signal-forms-drop-the-atom-into-cngx-form-field

Metadata#

Host#

Providers#

CNGX_CONTROL_VALUE
useExisting CngxChipInteraction
CNGX_FORM_FIELD_CONTROL
useExisting CngxChipInteraction

Relationships

Index#

Inputs#

chipValue#T
input()Required

Chip 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.
default { alias: 'value' }
describedBy#string | null
input()

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

default null, { alias: 'cngxDescribedBy', }
disabled#boolean
model()
default false
disabledReason#string
input()
default ''
errorMessageId#string | null
input()

Optional 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 null skips the attribute entirely. Note: WAI-ARIA dictates that AT ignores this attribute when aria-invalid is absent or "false", so a stable always-emitted id is harmless when the field is valid.
default null
invalid#boolean
model()

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.

default false
model()

Form-bound selection state. Aliased so consumers write [(selected)]="b"; the field name is value to satisfy the CngxControlValue<boolean> contract used by CngxFormBridge (Phase 7). See class JSDoc.

default false, { alias: 'selected' }

Outputs#

disabled#boolean
model()
invalid#boolean
model()

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.

removeRequest#void
output()

Fires on Backspace/Delete keydown - consumer owns the removal.

model()

Form-bound selection state. Aliased so consumers write [(selected)]="b"; the field name is value to satisfy the CngxControlValue<boolean> contract used by CngxFormBridge (Phase 7). See class JSDoc.

Instance Properties#

focused#unknown
Readonly
this.focusedState.asReadonly()
id#unknown
signal()Readonly
signal(nextUid('cngx-chip-')).asReadonly()

Methods#

handleClick#void
Protected
handleClick(event: MouseEvent)
@parameventMouseEvent
handleFocusIn#void
Protected
handleFocusOut#void
Protected
handleKeydown#void
Protected
handleKeydown(event: Event)
@parameventEvent
handleRemoveKeydown#void
Protected
handleRemoveKeydown(event: Event)
@parameventEvent

HostBindings#

BindingExpression
[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#

EventHandler
(click)click()
(keydown.space)keydown.space()
(keydown.enter)keydown.enter()
(keydown.delete)keydown.delete()
(keydown.backspace)keydown.backspace()
(focusin)focusin()
(focusout)focusout()