ControlValueAccessor
(CVA) — это интерфейс, который позволяет создавать пользовательские элементы управления, полностью интегрируемые с Angular Forms (как реактивными, так и шаблонными). Это мощный инструмент для создания переиспользуемых компонентов ввода.
Для создания кастомного элемента управления нужно:
ControlValueAccessor
NG_VALUE_ACCESSOR
import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-custom-input',
template: `
<input
[type]="type"
[value]="value"
[disabled]="disabled"
(input)="onInput($event)"
(blur)="onBlur()"
>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent implements ControlValueAccessor {
@Input() type = 'text';
value: string = '';
disabled = false;
// Функции для взаимодействия с Angular Forms
onChange: any = () => {};
onTouched: any = () => {};
// Вызывается при изменении значения в форме
writeValue(value: string): void {
this.value = value || '';
}
// Регистрируем callback на изменения
registerOnChange(fn: any): void {
this.onChange = fn;
}
// Регистрируем callback на "touched"
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
// Устанавливаем disabled состояние
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
// Обработчик ввода
onInput(event: Event): void {
const newValue = (event.target as HTMLInputElement).value;
this.value = newValue;
this.onChange(newValue); // Сообщаем Angular Forms о изменении
}
// Обработчик blur
onBlur(): void {
this.onTouched(); // Сообщаем Angular Forms о "touched"
}
}
<!-- С реактивными формами -->
<form [formGroup]="form">
<app-custom-input formControlName="username"></app-custom-input>
</form>
<!-- С ngModel -->
<app-custom-input [(ngModel)]="userName"></app-custom-input>
@Component({
selector: 'app-toggle',
template: `
<div
(click)="toggle()"
[class.checked]="value"
[class.disabled]="disabled"
>
{{ value ? 'On' : 'Off' }}
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ToggleComponent),
multi: true
}
]
})
export class ToggleComponent implements ControlValueAccessor {
value = false;
disabled = false;
onChange: any = () => {};
onTouched: any = () => {};
writeValue(value: boolean): void {
this.value = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
toggle(): void {
if (this.disabled) return;
this.value = !this.value;
this.onChange(this.value);
this.onTouched();
}
}
Кастомные элементы с CVA автоматически поддерживают валидацию:
this.form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
notifications: [false, Validators.requiredTrue]
});
Для сложных элементов (например, адреса с несколькими полями):
@Component({
selector: 'app-address-input',
template: `
<input [(ngModel)]="address.street" (ngModelChange)="onChange()">
<input [(ngModel)]="address.city" (ngModelChange)="onChange()">
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AddressInputComponent),
multi: true
}
]
})
export class AddressInputComponent implements ControlValueAccessor {
address = { street: '', city: '' };
writeValue(value: any): void {
this.address = value || { street: '', city: '' };
}
onChange(): void {
this.onChangeCallback(this.address);
}
// ... остальные методы CVA
}
ChangeDetectionStrategy.OnPush
onChange
(можно добавить debounce)writeValue
- для получения значения из формыregisterOnChange
- регистрация callback на измененияregisterOnTouched
- регистрация callback на touchedsetDisabledState
- обработка disabled состоянияNG_VALUE_ACCESSOR
провайдерПравильная реализация CVA позволяет создавать мощные переиспользуемые компоненты, которые идеально интегрируются в экосистему Angular Forms.