DEV Community

Cover image for Creating Type-Safe Forms in Angular: Advanced Tip 🤯
Erik Henrique
Erik Henrique

Posted on • Edited on

Creating Type-Safe Forms in Angular: Advanced Tip 🤯

When developing complex applications in Angular, one common challenge is dealing with forms and their validation. As applications grow, so does the complexity of forms, leading to potential errors and maintenance difficulties. To address these challenges, Angular provides a powerful form handling module, @angular/forms, which allows developers to create dynamic and type-safe forms. In this tutorial, we will explore a helpful utility type created by me called WithControlsFrom that enhances the form-building process by providing type-safe controls for form fields.

Introduction to WithControlsFrom type

The WithControlsFrom type is a versatile utility that assists in creating type-safe forms using the @angular/forms module. It simplifies the process of binding form controls to an interface, ensuring that the form's structure aligns with the interface properties. This utility type generates a type that defines each property of the interface as an optional form control. This way, you can avoid common errors like assigning an incorrect data type to a form control or referencing a non-existing property.

Let's dive into an example to see how WithControlsFrom can be used effectively.

Example: UserModel Form

Suppose we have an interface UserModel representing user data:

export interface UserModel {
  id: number | null;
  name: string;
  gender: string;
}
Enter fullscreen mode Exit fullscreen mode

We want to create a form for capturing user information using the @angular/forms module. Here's how we can utilize the WithControlsFrom type to achieve this:

import { Component } from '@angular/core';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';

// 👇 This bind any interface with the FormGroup generating a type-safe form matching existing keys and FormControl type 🙂
export type WithControlsFrom<T> = {
  [P in keyof T]?: FormControl<T[P]> | FormArray<FormControl<T[P]>> | FormGroup<{ [K in keyof T]: FormControl<T[K]> }>;
};

@Component({
  selector: 'app-user-form',
  template: `
    <form [formGroup]="userForm">
      <label>
        ID:
        <input type="number" formControlName="id" />
      </label>
      <label>
        Name:
        <input type="text" formControlName="name" />
      </label>
      <label>
        Gender:
        <input type="text" formControlName="gender" />
      </label>
    </form>
  `,
})
export class UserFormComponent {
  userForm!: FormGroup<WithControlsFrom<UserModel>>;

  constructor(private readonly _formBuilder: FormBuilder) {
    this.userForm = this._formBuilder.group<WithControlsFrom<UserModel>>({
      id: this._formBuilder.control(null),
      name: this._formBuilder.control('Father Horse'),
      gender: this._formBuilder.control(777), // Throws: Type 'FormControl<number>' is not assignable to type 'FormControl<string>'. Type 'number' is not assignable to type 'string'.
      xyz: this._formBuilder.control(''), // Throws: Object literal may only specify known properties, and 'xyz' does not exist in type 'WithKeysFrom<UserModel>'.
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

In this example, we've created a UserFormComponent that defines a form group named userForm. The form group is constructed using the WithControlsFrom type, which ensures that the form controls correspond to the properties of the UserModel interface. Each property is mapped to a corresponding form control, and the form's HTML template uses the formControlName directive to bind the form controls to the input elements.

Conclusion

In this tutorial, we explored the WithControlsFrom utility type, which greatly simplifies the process of creating type-safe forms in Angular applications. By leveraging this utility, developers can ensure that their form controls align with the structure of the underlying data model, reducing the risk of errors and improving code maintainability. This type-safe approach to form creation enhances the development experience and contributes to the overall robustness of Angular applications.

For more information on working with forms in Angular, you can refer to the official Angular documentation.

Remember that using WithControlsFrom can significantly enhance the reliability and maintainability of your forms, ensuring that your code remains consistent with your data models. Happy coding!

References:

Disclaimer

This article was made with a little help from ChatGPT.

Top comments (2)

Collapse
 
erikunha profile image
Erik Henrique • Edited

@rajanchavda Good catch, I think you can just follow the same idea and extend like:

export type WithControlsFrom<T> = {
  [P in keyof T]?: FormControl<T[P]> | FormArray<FormControl<T[P]>> | FormGroup<{ [K in keyof T]: FormControl<T[K]> }>;
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
rajanchavda profile image
Rajan Chavda

This will not work if we want to use FormArray inside a FormGroup using common type WithControlsFrom, right ?