CngxBindField
projects/forms/field/bind-field.directive.ts
Import#
import { CngxBindField } from '@cngx/forms/field'
Description#
Universal bridge that connects any control - Material (<mat-select>,
<mat-chip-grid>, …), native (<input>, <select>, <textarea>), a
custom Signal-Forms FormValueControl<T>, or a legacy Reactive-Forms CVA -
to a surrounding <cngx-form-field>.
Place on the same element as the control. The directive derives id,
empty, focused, disabled, and errorState purely from the field
via the presenter - it never injects the concrete control, so it works
uniformly across control types.
Value flow is out of scope here: it runs through the host element's own
bindings ([control] for Signal Forms, [formControl] for Reactive
Forms). This directive only projects form-field ARIA/state onto the host.
For cngx-native atoms with exotic value semantics (multi-select,
compareWith, …), write a specialised bridge (see CngxListboxFieldBridge).
Usage (Signal Forms + mat-select)
<cngx-form-field [field]="f.color">
<label cngxLabel>Color</label>
<mat-select cngxBindField [control]="f.color">
<mat-option value="red">Red</mat-option>
<mat-option value="green">Green</mat-option>
</mat-select>
<cngx-field-errors />
</cngx-form-field>Usage (Reactive Forms + custom control)
<cngx-form-field [field]="adapted">
<label cngxLabel>Phone</label>
<my-phone-input cngxBindField [formControl]="ctrl" />
<cngx-field-errors />
</cngx-form-field>Metadata#
Host#
Providers#
CNGX_FORM_FIELD_CONTROL- useExisting
CngxBindField
Relationships
Depends on1
Index#
Properties
Derived State
HostBindings
Instance Properties#
HostBindings#
| Binding | Expression |
|---|---|
[id] | id() |
[attr.aria-describedby] | describedBy() |
[attr.aria-labelledby] | labelledBy() |
[attr.aria-invalid] | ariaInvalid() |
[attr.aria-required] | ariaRequired() |
[attr.aria-busy] | ariaBusy() |
[attr.aria-errormessage] | ariaErrorMessage() |
[attr.aria-readonly] | ariaReadonly() |
HostListeners#
| Event | Handler |
|---|---|
(focusin) | focusin() |
(focusout) | focusout() |
Material select via cngxBindField
import { ChangeDetectionStrategy, Component, DestroyRef, ViewEncapsulation, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { toSignal } from '@angular/core/rxjs-interop';
import { MatSelectModule } from '@angular/material/select';
import {
CngxFormField,
CngxLabel,
CngxFieldErrors,
CngxBindField,
adaptFormControl,
CNGX_ERROR_MESSAGES,
} from '@cngx/forms/field';
/**
* The universal bridge on a third-party control. `[cngxBindField]` projects the
* `cngx-form-field` ARIA/state onto a bare `mat-select` (a self-contained
* Material widget - no `mat-form-field` needed). `cngxLabel` owns the label/id;
* `cngx-field-errors` renders validation.
*
* Value flow is NOT cngx's job here - it runs on the control's own
* `[formControl]` binding. Material couples to Reactive Forms (mat-select is a
* CVA control), so the field is a `FormControl` lifted into a Signal-Forms-shaped
* accessor via `adaptFormControl`. The readout shows the bridge-derived signals
* (`id`/`focused`/`empty`/`errorState`) next to the RF control state.
*/
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
imports: [ReactiveFormsModule, MatSelectModule, CngxFormField, CngxLabel, CngxFieldErrors, CngxBindField],
providers: [{ provide: CNGX_ERROR_MESSAGES, useValue: { required: () => 'Please choose a size' } }],
styleUrl: './material-bind-field.component.scss',
template: `
<div class="demo">
<p class="lead">
<code>[cngxBindField]</code> instruments a bare <code>mat-select</code>: Material renders
the trigger, cngx derives the field state. Value via Reactive Forms.
</p>
<div class="block">
<cngx-form-field [field]="sizeField">
<label cngxLabel>Size</label>
<mat-select
cngxBindField
#bf="cngxBindField"
[formControl]="sizeControl"
placeholder="Choose…"
class="size-select"
>
<mat-option value="s">Small</mat-option>
<mat-option value="m">Medium</mat-option>
<mat-option value="l">Large</mat-option>
<mat-option value="xl">X-Large</mat-option>
</mat-select>
<cngx-field-errors />
</cngx-form-field>
<dl class="state">
<dt>id</dt><dd>{{ bf.id() }}</dd>
<dt>focused</dt><dd>{{ bf.focused() }}</dd>
<dt>empty</dt><dd>{{ bf.empty() }}</dd>
<dt>errorState</dt><dd>{{ bf.errorState() }}</dd>
<dt>value</dt><dd>{{ value() || '—' }}</dd>
<dt>touched</dt><dd>{{ sizeControl.touched }}</dd>
<dt>valid</dt><dd>{{ sizeControl.valid }}</dd>
</dl>
</div>
</div>
`,
})
export class MaterialBindFieldExample {
private readonly destroyRef = inject(DestroyRef);
protected readonly sizeControl = new FormControl<string>('', {
validators: [Validators.required],
nonNullable: true,
});
// Lift the Reactive Forms control into a Signal-Forms-shaped accessor so
// cngx-form-field can consume it via [field].
protected readonly sizeField = adaptFormControl(this.sizeControl, 'size', this.destroyRef);
protected readonly value = toSignal(this.sizeControl.valueChanges, {
initialValue: this.sizeControl.value,
});
}
export { MaterialBindFieldExample as AppComponent };