TypeScript’s static typing is one of the main attractions for many developers, yet Angular Reactive Forms have historically lagged behind in this area.
The heavy reliance on the any keyword within Angular’s form classes often leads to frustrating development experiences. Issues like incorrect typecasting, subtle typos in control names, and a general lack of compile-time safety can quickly make form-related code messy and error-prone.
In a recent discussion about the challenges of making Angular Reactive Forms strongly typed, an effective solution emerged that follows the Interface Segregation Principle. This approach addresses the problem elegantly without requiring any changes to the existing form class structure.
Before diving into the how, let’s take a snapshot of the current hurdles.
Investigating the Obstacles
In any growing enterprise application, forms are a constant presence. This often prompts developers to create custom solutions to enforce type safety and catch errors at build time rather than runtime. The typical high-level problems being tackled are:
- A typo in a control name does not trigger a build-time error.
- There are frequent typecasting issues when subscribing to
valueChangesandstatusChangeson aFormControl. - Managing nested
FormGroupandFormArraystructures is difficult due to a lack of proper type information and casting.
To solve these, one common approach is to create custom generic classes that extend the core Angular base classes (FormGroup, FormControl, and FormArray). While this helps with type safety, it introduces a new set of challenges:
- You lose the convenient and elegant use of the
FormBuilderservice. - A small mistake in your custom generic class can have widespread, unexpected consequences throughout the application.
- It creates a risk that different parts of the codebase will use mixed approaches, with some
FormGroupinstances created via custom classes and others using the standard base classes.
The goal is to find a solution that overcomes these drawbacks. Ideally, it should offer full type safety without adding to the application’s bundle size (0 Bytes).
Strong Typing Without Generic Classes
The key is to leverage TypeScript’s interface features to define and enforce the form’s shape, all while sticking to the Interface Segregation Principle.
How to Achieve It
The solution involves using the @rxweb/types package. It’s a collection of type definitions that can transform our reactive forms into strongly-typed ones, and since it only contains types, there is no runtime code that could break.
Let’s examine a typical scenario with a FormControl, a nested FormGroup, and a nested FormArray. Here’s how the FormGroup is traditionally set up:
export class AppComponent implements OnInit {
formGroup: FormGroup;
formBuilder: FormBuilder;
constructor(formBuilder: FormBuilder) {
this.formBuilder = formBuilder;
}
ngOnInit() {
this.formGroup = this.formBuilder.group({
firstName: ['', [Validators.required]],
address: this.formBuilder.group({
countryName: ["", Validators.required]
}),
skills: this.formBuilder.array([
this.formBuilder.group({
name: ["", Validators.required]
})
])
});
}
}
To convert this form to a strongly-typed version, we simply follow four straightforward steps.
Step 1: Install the Package
npm install @rxweb/types
Step 2: Create Interfaces
We define the control names as properties in respective interfaces: User has firstName, address, and skills; Address has countryName; and Skill has name.
Step 3: Import Generic Interfaces
We need to import two specific interfaces from the package:
- IFormGroup: This offers a strongly-typed version of the
FormGroupAPIs. - IFormBuilder: This allows us to use
FormBuilderto createFormGroup,FormControl, andFormArrayinstances with full type safety.
The import and usage look like this:
import { IFormGroup, IFormBuilder } from “@rxweb/types”;
Step 4: Convert the Form
The transformation process itself is quick and simple:
- Change the explicit type of your form from
FormGrouptoIFormGroup. - Change the type of the
FormBuilderservice toIFormBuilder. - Pass the corresponding interface as a generic parameter to the respective methods:
group<User>,group<Address>, andarray<Skill>.
That’s all there is to it.
Here is the converted code:
formGroup: IFormGroup<User>;
formBuilder: IFormBuilder;
ngOnInit() {
this.formGroup = this.formBuilder.group<User>({
firstName: ['', [Validators.required]],
address: this.formBuilder.group<Address>({
countryName: ["", Validators.required]
}),
skills: this.formBuilder.array<Skill>([
this.formBuilder.group({
name: ["", Validators.required]
})
])
});
}
All the previously mentioned challenges are resolved with these simple steps. Below is a quick look at how typos and type mismatches are now caught during the build process.
Typo Mistake

Type Mismatch

For a more hands-on look, you can check this stackblitz example to explore the strongly-typed Reactive Form APIs in action. More comprehensive details are available in the official documentation.
Conclusion
The most compelling aspect is that we can achieve this level of type safety purely through interfaces, without altering any class definitions. However, if your needs extend beyond just strong typing to address issues like code duplication and following practices such as Single Responsibility and Domain-Driven Design, we recommend referring to the article “New Way to Validate the Angular Reactive Form”.
I hope this guide proves useful. If you have any thoughts or questions, feel free to leave a comment below.
