# Angular Signal Forms - ( FormValueControl ) ## Table of Contents - [Signal Form FormValueControl](#formValueControl) ## Signal Forms FormValueControl ```typescript interface Rating { rating: number; } import { form, FormField, FormValueControl, ValidationError, WithOptionalField } from '@angular/forms/signals'; import { MatIconModule } from '@angular/material/icon'; import { MatError } from '@angular/material/form-field'; @Component({ selector: 'app-rating', imports: [MatIconModule, MatError], template: `
@for (star of starArray(); track $index) { {{ getStarIcon(star) }} } @if (errors().at(0)?.message) { {{ errors().at(0)?.message }} }
`, styles: ``, }) export class Rating implements FormValueControl { // Required: The value of the control, exposed as a two-way binding. readonly value = model(0); // Optional: Bindings for other form control states. readonly readonly = input(false); readonly invalid = input(false); readonly errors: InputSignal[]> = input[]>([]); starArray: Signal = signal( Array(5) .fill(0) .map((_, i) => i + 1), ); getStarIcon(index: number): string { const floorRating = Math.floor(this.value()); if (index <= floorRating) { return 'star'; // Full star } else { return 'star_border'; // Empty star } } rate(index: number): void { if (!this.readonly()) { this.value.set(index); } } } import { FormField } from '@angular/forms/signals'; @Component({ selector: 'app-signal-forms', imports: [FormField, Rating], template: `
{{ ratingForm.rating().value() }}
`, styles: ``, }) export class SignalForms { readonly ratingModel = signal({ rating: 0, }); readonly ratingForm = form(this.ratingModel); submit(event: Event): void { event.preventDefault(); console.log(this.ratingForm.rating().value()); } } ```