Lesson 24 of 30
A signal-based form system, stable in Angular 22 — the emerging direction, not yet what most codebases run. State lives in one model signal; the form is derived from it.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Signal Forms is the newer, signal-based alternative to FormGroup/FormControl — it reached stable in Angular 22. It's worth being honest about where it sits: this is the emerging direction the team is pushing toward, not yet what most existing Angular codebases actually run. Reactive Forms will keep being the mainstream default for a while; Signal Forms is the stable-but-new option you should know about, especially for interviews and greenfield work.
The core idea: instead of building a separate tree of FormControl/FormGroup class instances that live alongside your data, Signal Forms derives the entire form — fields, validity, errors — from a single model signal that holds the plain data object. You call form(model, schema) once; validation is expressed as composable functions applied to paths inside that schema, instead of a Validators array bolted onto each control. There's no second parallel object tree to keep in sync with your data — the form is a reactive view over the model signal you already have.
import { Component, signal } from '@angular/core';
import { form, Field, required, minLength, email } from '@angular/forms/signals';
interface SignupModel {
email: string;
password: string;
}
@Component({
selector: 'app-signup-form-signals',
imports: [Field],
template: `
<form (submit.prevent)="submit()">
<input [field]="signupForm.email" placeholder="Email" />
@if (signupForm.email().invalid()) {
<p class="error">Enter a valid email.</p>
}
<input [field]="signupForm.password" type="password" placeholder="Password" />
@if (signupForm.password().invalid()) {
<p class="error">At least 8 characters.</p>
}
<button type="submit" [disabled]="signupForm().invalid()">Sign up</button>
</form>
`,
})
export class SignupFormSignalsComponent {
model = signal<SignupModel>({ email: '', password: '' });
signupForm = form(this.model, (path) => {
required(path.email);
email(path.email);
required(path.password);
minLength(path.password, 8);
});
submit() {
if (this.signupForm().invalid()) return;
console.log(this.model());
}
}
model is the single source of truth — a plain { email, password } signal, nothing form-specific about its shape. form(this.model, schema) derives a field tree from it: signupForm.email and signupForm.password are fields you bind directly in the template with [field], and each field itself is a signal you call to read .invalid(), .value(), and so on. The validation schema — required, email, minLength — is just composable functions applied to paths, not validator objects attached to controls.
FormGroup/FormControl. Know both, and be honest about which one is "current mainstream" versus "current stable option."signupForm.email is a field — reading its validity or value means calling it, signupForm.email().invalid(), the same signal-calling habit as everywhere else in the signals model, not a special forms-only exception.This lesson is the reactive forms lesson's problem — structured, validated form state — solved with the signals fundamentals lesson's primitive instead of a parallel AbstractControl class hierarchy. It's the same shift as the zoneless change detection lesson in miniature: replace a purpose-built object model with the same signal graph that already drives the rest of the app.