
Godinama smo se oslanjali na Control Value Accessor.
Sa Signal Formama dolazi novi interfejs koji treba da implementiramo.
Od ControlValueAccessor do FormValueControl
Sa klasičnim Reactive Forms, ako ste želeli custom polje u formi (date picker, rich text editor, dropdown, itd.), morali ste da:
- implementirate
ControlValueAccessor - registrujete ga sa
NG_VALUE_ACCESSOR - ručno implementirate
writeValue,registerOnChange,setDisabledState, itd.
Sa Signal Formama, ugovor se prebacuje na interfejse umesto neprozirnih callback-ova:
FormUiControl— opcioni UI/status inputi (errors, disabled, touched, itd.)FormValueControl<TValue>— glavni ugovor za kontrole koje menjaju vrednost
Ako vaša komponenta implementira FormValueControl<TValue>, Field direktiva zna kako da poveže polje sa njom.
Šta ovi interfejsi zaista znače
FormUiControl
Ovo je set inputa koje je "lepo imati". Svaki property je:
- opcion
InputSignal<…>(ili slično)- automatski povezan od strane
Fielddirektive ako je prisutan
Primeri:
errors?: InputSignal<ValidationError[]>
→ Ako ga implementirate, Field će gurnuti validacione greške u vašu komponentu.
disabled?: InputSignal<boolean>
→ Ako je implementirano, vaša komponenta će primati true/false na osnovu stanja polja.
touched?, dirty?, invalid?, pending?, required?, min, max, minLength, maxLength, pattern, itd.
→ Sve ovo omogućava pravljenje super pametnih custom komponenti koje u potpunosti reflektuju stanje polja bez vašeg ručnog povezivanja.
Možete implementirati nijednu, neke, ili sve od ovih. Samo value je obavezan — a to se nalazi na sledećem interfejsu.
FormValueControl<TValue>
Ovo je osnovni ugovor za "normalno" custom polje:
interface FormValueControl<TValue> extends FormUiControl {
readonly value: ModelSignal<TValue>;
readonly checked?: undefined;
}
Ključne tačke:
-
value: ModelSignal<TValue>je obavezan
→ Ovo je dvosmerno povezivanje (two-way binding) između vaše komponente i forme. -
checked?: undefinedje ovde eksplicitno zabranjen
→ To je rezervisano za drugačiji ugovor (komponente u stilu checkbox-a).
Dakle:
Ako vaša komponenta ima value model signal, može se koristiti sa [field].
Bez writeValue, bez registerOnChange, bez provajdera. Samo model signal.
Minimalni primer: Custom input sa Signal Formama
Zamislite jednostavnu custom input komponentu:
import { Component, model, input } from '@angular/core';
import { Field } from '@angular/forms/signals';
import type { FormValueControl } from '@angular/forms/signals';
@Component({
selector: 'app-fancy-input',
standalone: true,
template: `
<label class="flex flex-col gap-1">
<span class="text-sm font-medium">Fancy input</span>
<input
class="border rounded px-3 py-2"
[value]="value()"
(input)="value.set($any($event.target).value)"
[disabled]="disabled() ?? false"
/>
</label>
`,
})
export class FancyInputComponent implements FormValueControl<string> {
// obavezno za FormValueControl
readonly value = model<string>('');
// opciono, iz FormUiControl — Field će ih povezati ako su prisutni
readonly errors = input<ReadonlyArray<{ message: string }> | undefined>();
readonly disabled = input<boolean>();
readonly invalid = input<boolean>();
}
Zatim to možete koristiti u Signal Formi na sledeći način:
import { Component, signal } from '@angular/core';
import { form, Field } from '@angular/forms/signals';
import { FancyInputComponent } from './fancy-input.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [Field, FancyInputComponent],
template: `
<form>
<app-fancy-input [field]="form.name"></app-fancy-input>
</form>
`,
})
export class AppComponent {
private readonly model = signal({ name: '' });
protected readonly form = form(this.model);
}
To je to. Nema CVA, nema NG_VALUE_ACCESSOR.