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