Angular Strictly Typed Forms & NonNullableFormBuilder (Introduced in Angular 14)

April 5, 2025 8:20 PM

Angular Strictly typed forms
post image

In previous Angular versions, most of these APIs included any somewhere in their types, and interacting with the structure of the controls, or the values themselves, was not type-safe. For example: you could write the following invalid code:

const emailDomain = login.value.email.domain;

Ref. Angular Official Doc: https://angular.dev/guide/forms/typed-forms

Angular's strictly typed forms bring type safety to reactive forms, ensuring that the values and structure of forms align with TypeScript types. This feature improves developer productivity and reduces runtime errors.


  1. Strongly Typed Form Controls

    1. Form controls (FormControl, FormGroup, FormArray) are now strictly typed, ensuring that the values match the expected types.
  2. Type Inference

    1. When using FormBuilder, Angular can infer the types of form controls based on their initial values.

  3. Improved Developer Experience

    1. Autocompletion and type checking in IDEs help catch errors during development.

  4. Custom Types for Form Groups

    1. You can define custom interfaces for your form structure to ensure type safety.

  5. Backward Compatibility

    • Strictly typed forms are opt-in, so existing forms without types will still work as before.

Benefits of Strictly Typed Forms

  • Type Safety: Prevents runtime errors by ensuring form values match their expected types.
  • Better Autocompletion: IDEs provide better suggestions for form controls and values.
  • Improved Maintainability: Makes it easier to refactor and debug forms in large applications.
  • Reduced Boilerplate: Type inference reduces the need for explicit type annotations.

How to Enable Strictly Typed Forms

Strictly typed forms are enabled by default in Angular 14+ when you create a new project. For existing projects, you can update your forms to use the new types by explicitly typing your FormControl, FormGroup, and FormArray.


Example: Strictly Typed Signup Form


Let's say we have not provided any types but still with the help of type inference mechanism, Angular will provide you the best auto-completion while accessing the form values.

image_4.png

But you explicitly provide the type for the singupForm as FormGroup.

signupForm: FormGroup = this.fb.group();

It will loose the auto completion or suggestion when accessing the value. This happens because we have not provided any type to the singupForm and hence it understand it as

singupForm: FormGroup<any> = this.fb.group();


Untyped Forms

In Angular, typed forms were introduced to improve type safety in reactive forms. However, untyped forms are still supported for backward compatibility. Here's a simple explanation:

  • Untyped forms work the same way as forms in older Angular versions.
  • You must explicitly use the Untyped prefix for form classes like UntypedFormGroup and UntypedFormControl.
const loginForm = new UntypedFormGroup({
  email: new UntypedFormControl(''), // No type checking
  password: new UntypedFormControl(''),
});


If we see the type for this simplest form control

const email = new FormControl('abc@gmail.com');

This control will be automatically inferred to have the type FormControl<string|null>. TypeScript will automatically enforce this type throughout the FormControl API, such as email.value, email.valueChanges, email.setValue(...), etc.


Nullability

Angular's FormControl includes null in its type because a control's value can become null when the reset() method is called.

 For example, if you create a FormControl with an initial value of 'abc@gmail.com' and then call reset(), the value will become null. This behavior ensures that TypeScript enforces handling the possibility of null values in your code.

const email = new FormControl('abc@gmail.com');
email.reset();
console.log(email.value); // null


To make a FormControl non-nullable, you can use the nonNullable option. When this option is enabled, the control resets to its initial value instead of null. For instance, if you create a FormControl with the nonNullable option and an initial value of 'abc@gmail.com', calling reset() will reset the value back to 'abc@gmail.com' instead of null. This approach affects the runtime behavior of your form when reset() is called and should be used carefully to ensure it aligns with your application's requirements.

const email = new FormControl('abc@gmail.com', {nonNullable: true});
email.reset();
console.log(email.value); // abc@gmail.com

Specifying an Explicit Type

It is possible to specify the type, instead of relying on inference. Consider a control that is initialized to null. Because the initial value is null, TypeScript will infer FormControl<null>, which is narrower than we want.

const email = new FormControl(null);
email.setValue('abc@gmail.com'); // Error!

To prevent this, we explicitly specify the type as string|null

const email = new FormControl<string|null>(null);
email.setValue('abc@gmail.com');

FormArray: Dynamic, Homogenous Collections

A FormArray contains an open-ended list of controls. The type parameter corresponds to the type of each inner control:

const names = new FormArray([new FormControl('Rohit')]);
names.push(new FormControl('Rolly'));

This FormArray will have the inner controls type FormControl<string|null>.

If you want to have multiple different element types inside the array, you must use UntypedFormArray, because TypeScript cannot infer which element type will occur at which position.


Understanding Angular's Typed Forms: FormGroup, FormRecord, and NonNullableFormBuilder

Angular's reactive forms have evolved significantly with the introduction of typed forms in Angular 14. These enhancements bring type safety, flexibility, and better developer experience to form handling. In this article, we’ll explore key concepts like FormGroup, FormRecord, Partial values, and the NonNullableFormBuilder.

FormGroup and FormRecord

Angular provides two main types for managing forms:

  1. FormGroup: Used for forms with a predefined set of keys. Each key corresponds to a specific form control.

  2. FormRecord: Designed for forms with dynamic or open-ended keys, where the keys are not known ahead of time.


    • When to Use FormRecord
      : If your form needs to handle dynamic keys (e.g., a list of addresses or user-defined fields), FormRecord is the ideal choice.

Partial Values in FormGroup

In Angular, you can disable form controls, and any disabled control will not appear in the form's value. This behavior makes the form's value a partial object, meaning some fields might be undefined.

If you disable the password control:

The type of login.value becomes:

Here, Partial means that each field in the object might be undefined. For example, login.value.password could be undefined.


Accessing Raw Values

If you want to include disabled controls in the form's value, you can use the getRawValue() method. This method bypasses the Partial type and returns all controls, including disabled ones.


Optional Controls and Dynamic Groups

Some forms may have controls that are optional or added/removed dynamically at runtime. You can represent these controls using optional fields in TypeScript.

Here, TypeScript ensures that only optional controls (like password) can be removed or added dynamically.


FormRecord for Open-Ended Forms

When the keys of a form are not known in advance, FormRecord is the best choice. It allows you to dynamically add controls with flexible keys.

If your form needs to handle both dynamic keys and heterogeneous control types, you should use UntypedFormGroup for maximum flexibility.


NonNullableFormBuilder

The NonNullableFormBuilder is a shorthand for creating forms where all controls are non-nullable. This eliminates the need to repeatedly specify { nonNullable: true } for each control.

In this example, both email and password controls are non-nullable by default. This is particularly useful for large forms where you want to avoid boilerplate code.


Key Differences Between FormGroup and FormRecord

FeatureFormGroupFormRecord
KeysPredefined and enumerated.Dynamic and open-ended.
Use CaseFixed forms with known structure.Dynamic forms with unknown keys.
ExampleLogin form with email and password.Address book with user-defined keys.

When to Use Typed Forms

Typed forms are ideal for ensuring type safety and reducing runtime errors. They are particularly useful in scenarios where:

  • You need strict control over form values and types.
  • You want better autocompletion and error detection in your IDE.
  • You are working with complex or dynamic forms.

For older projects, you can still use UntypedFormGroup and UntypedFormControl for backward compatibility.



Angular's typed forms, including FormGroup, FormRecord, and NonNullableFormBuilder, provide powerful tools for building robust and type-safe forms. Whether you're working with fixed forms or dynamic, open-ended forms, these features ensure better developer experience and maintainability. By leveraging these tools, you can create forms that are both flexible and reliable.

Comments


    Read next