NgxControlValueAccessor
NgxControlValueAccessor
is a directive to reduce boilerplate when building components, which implement the ControlValueAccessor interface.
Import
import { NgxControlValueAccessor } from 'ngxtension/control-value-accessor';
Usage
NgxControlValueAccessor
implements the ControlValueAccessor interface and exposes a simpler api. Declare NgxControlValueAccessor
in the hostDirectives
section of your component and inject the instance in order to wire up your template:
NgxControlValueAccessor.value
for syncing the valueNgxControlValueAccessor.disabled
for syncing the disabled stateNgxControlValueAccessor.markAsTouched
for marking the view as touched
The value and disabled state are also available as signals:
NgxControlValueAccessor.value$
NgxControlValueAccessor.disabled$
Example
In this example NgxControlValueAccessor
is used to create a CustomInput
component.
@Component({ selector: 'custom-input', hostDirectives: [NgxControlValueAccessor], template: ` <label> <b>Custom label</b> <input type="text" (input)="cva.value = $event.target.value" [value]="cva.value$()" [disabled]="cva.disabled$()" (blur)="cva.markAsTouched()" /> </label> `, standalone: true,})export class CustomInput { protected cva = inject<NgxControlValueAccessor<string>>( NgxControlValueAccessor, );}
With usage:
<custom-input [formControl]="control" /><custom-input [(ngModel)]="value" />
Non Primitive Values
When your model is a non primitive datatype, you should provide a comparator. It is a pure function which tells NgxControlValueAccessor
, whether two values are semantically equal:
(a, b) => boolean;
Example
In this example NgxControlValueAccessor
is used to create a User
select. A User
is identified by its id
.
interface User { id: string; name: string;}
const userComparator: NgxControlValueAccessorCompareTo<User> = (a, b) => a?.id === b?.id;
provideCvaCompareTo(userComparator, true);
// or
provideCvaCompareToByProp<User>('id');
Full example:
@Component({ selector: 'user-select', standalone: true, hostDirectives: [NgxControlValueAccessor], providers: [provideCvaCompareToByProp<User>('id')], template: ` <label> <b>Select a user:</b> <select [disabled]="cva.disabled$()" (blur)="cva.markAsTouched()" (change)="onChange($event)" > <option [selected]="cva.value === null">-- no user selected --</option> @for (user of users; track user.id) { <option [value]="user.id" [selected]="user.id === cva.value?.id"> {{ user.name }} </option> } </select> </label> `,})export class UserSelect { protected cva = inject<NgxControlValueAccessor<User | null>>( NgxControlValueAccessor, );
protected onChange = (event: Event) => (this.cva.value = this.users.find(({ id }) => event.target.value === id) ?? null);
@Input() users: User[] = [];}
With usage:
<user-select [formControl]="userControl" [users]="users" /><user-select [(ngModel)]="user" [users]="users" />
Without NgControl
Optionally you can expose inputs
and outputs
in the hostDirectives
declaration
and use it without a NgControl
directive.
hostDirectives: [ { directive: NgxControlValueAccessor, inputs: ['value'], outputs: ['valueChange'], },];
<custom-input [(value)]="value" />