# 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()); } } ```