FormControl<string> — Catch Bugs at Compile Time
Typed Forms
Angular forms have been strictly typed since v14. The type flows through value, valueChanges, and the controls object so misuse is a compile error.
What you'll learn
- Type a FormControl with a generic
- Type a FormGroup with FormBuilder
- Read a fully typed value
In old Angular, form.value was typed as any and controls['email'] returned a generic AbstractControl. Modern Angular fixes that — your forms are strongly typed.
Typing a FormControl
By default, a FormControl<T> is nullable because .reset() can set the value back to null.
import { FormControl } from '@angular/forms';
const email = new FormControl<string | null>('');
email.value; // string | null If you want a non-null value, pass { nonNullable: true }.
const email = new FormControl('', { nonNullable: true });
email.value; // string
email.reset(); // resets to '', not null Typing With FormBuilder
FormBuilder infers types from the initial values. There is also a nonNullable helper that flips the default.
import { inject } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
const fb = inject(FormBuilder);
const form = fb.nonNullable.group({
email: ['', [Validators.required, Validators.email]],
age: [0],
newsletter: [false],
});
form.value;
// { email?: string; age?: number; newsletter?: boolean }
form.getRawValue();
// { email: string; age: number; newsletter: boolean } Note that form.value keys are optional because disabled controls drop out. getRawValue() includes everything.
Typed Controls Access
form.controls.email.setValue('a@b.com'); // ok
form.controls.email.setValue(42); // compile error
form.controls.age.valueChanges.subscribe(n => {
// n is number
}); A Note on Migrations
Old code that used untyped forms can opt back in via UntypedFormControl and friends, but for new code stick with the typed APIs — the safety pays for itself the first time you rename a field.