Building reactive forms in Angular is one of the most powerful and type-safe ways to handle user input. But what happens when your form structure isn’t fixed — when the form controls depend on dynamic keys that you don’t know at compile time?
If you’ve tried to use FormGroup<T> in such cases, you’ve probably seen TypeScript complaining about missing keys. The solution? Say hello to FormRecord.
The Problem: Dynamic Keys and Type Errors
Let’s say you have a TypeScript interface for an address form:
interface AddressForm {
street: FormControl<string>;
city: FormControl<string>;
zip: FormControl<string>;
}
You can easily create a strongly typed form group:
const address = new FormGroup<AddressForm>({
street: new FormControl(''),
city: new FormControl(''),
zip: new FormControl(''),
});
This works perfectly when all keys (street, city, zip) are known at compile time.
But what if your backend sends different fields dynamically, for example:
{
address_line1: "123 Main St",
address_line2: "Apt 4B",
region: "Downtown"
}
You don’t know the exact field names ahead of time, and that’s where FormGroup<AddressForm> fails.
If you try something like:
const address = new FormGroup<AddressForm>({});
TypeScript throws an error:
❌ Type '{}' is missing the following properties from type 'AddressForm': street, city, zip
The Solution: Use FormRecord
Angular 14+ introduced FormRecord, designed specifically for dynamic sets of form controls.
You can think of it as a FormGroup that doesn’t require predefined keys. It’s a map of FormControls where the keys are strings determined at runtime.
Here’s how to use it:
const address = new FormRecord<FormControl<string | null>>({});
Now you can dynamically add controls:
address.addControl('address_line1', new FormControl('123 Main St'));
address.addControl('address_line2', new FormControl('Apt 4B'));
address.addControl('region', new FormControl('Downtown'));
✅ No compile-time errors.
✅ Full type safety.
✅ Works perfectly with dynamic form data.
When to Use FormGroup vs FormRecord
| Use Case | Recommended API | Description |
| Static forms (fixed fields known at compile time) | FormGroup<T> | Great for forms with predictable keys — benefits from strong TypeScript typing. |
| Dynamic forms (keys vary at runtime) | FormRecord<T> | Ideal for dynamic forms — allows adding/removing arbitrary controls safely. |
Example: Building a Dynamic Settings Form
Here’s a real-world example. Suppose you’re building a settings form where each key/value pair comes from an API:
interface Setting {
name: string;
value: string;
}
const settingsData: Setting[] = [
{ name: 'theme', value: 'dark' },
{ name: 'notifications', value: 'enabled' },
{ name: 'language', value: 'en' }
];
You can dynamically build your form like this:
const settingsForm = new FormRecord<FormControl<string>>({});
settingsData.forEach(setting => {
settingsForm.addControl(setting.name, new FormControl(setting.value));
});
And now you can use it in your template:
<form [formGroup]="settingsForm">
<div *ngFor="let key of settingsKeys">
<label>{{ key }}</label>
<input [formControlName]="key" />
</div>
</form>
With a simple getter:
get settingsKeys(): string[] {
return Object.keys(this.settingsForm.controls);
}
Summary
FormGroup<T> is strictly typed and great for known structures.
FormRecord<T> is flexible and perfect for dynamic or API-driven forms.
- Both support Angular’s reactive form APIs, validation, and change detection equally well.
If you’re working with dynamic data-driven forms, FormRecord is your new best friend.
✍️ Final Thoughts
Angular’s type-safe forms API has matured beautifully. The addition of FormRecord makes it possible to write flexible, dynamic forms without compromising on type safety — a big win for developer productivity and maintainability.