Angular reactive forms with strong type support

I have a significant number of components in my Angular project that I want to enhance with strongly typed FormGroups, FormArrays, and FormControls.

I am seeking advice on the best approach for implementing reactive forms with strong typing. Can anyone share their suggestions or recommendations based on personal experiences?

Thank you.

EDIT:

Just to clarify, when I create a FormGroup or FormArray, I currently do not have a way to define the structure of the form within it. This makes it challenging to maintain when passing the form to different components in my application.

Answer №1

One innovative approach involves utilizing TypeScript declaration files (*.d.ts) to extend generic interfaces that enhance standard form classes such as AbstractControl and FormControl. This method doesn't add any new functionality or increase the size of compiled JavaScript code, but it does enforce rigorous type checking.

This concept was originally proposed by Daniele Morosinotto in March of this year and discussions are currently underway to potentially incorporate it into Angular 9, as indicated by a tweet from IgorMinar.

Implementing this solution is quite simple:

  1. Download the TypedForms.d.ts file from this gist and save it as src/typings.d.ts in your project (for Angular 6+, no additional configuration is needed).
  2. Use the new types like FormGroupTyped<T> and FormControlTyped<T> whenever robust type validation is required (refer to examples in the provided gist or StackBlitz demo).

For further insights, you can explore a detailed blog post discussing various solutions for strongly typed forms.

Answer №2

For those seeking an alternative approach, I stumbled upon this insightful article discussing the concept of strong typing for Angular forms. Here is a brief summary:

interface Person {
  name: string;
  email: string
}

// Defining controls in a form group that will emit a Person as its value
type PersonControls = { [key in keyof Person]: AbstractControl };
type PersonFormGroup = FormGroup & { value: Person, controls: PersonControls };

export class MyFormComponent {
  form = new FormGroup({
    name: new FormControl(),
    email: new FormControl()
  } as PersonControls) as PersonFormGroup;

  initialize() {
    const name = this.form.controls.name; // now strongly typed!
  }
}

Answer №3

Exciting News for 2022 (Angular 14): Enhanced Typed Reactive Forms

Great news with the recent Angular update! Strict types are now available for reactive forms, eliminating the need for workarounds.

If you're using the formBuilder service to create your forms, you can easily assign a strict type like this:

type myType = 'typeA' | 'typeB' | 'typeC';

public myForm = this.fb.control<myType>('typeA');

If you prefer creating a formGroup without relying on the formBuilder service, here's how you can do it:

interface User {
    name: FormControl<string>;
    email: FormControl<string>;
    isAdmin: FormControl<boolean>;
}

public user: FormGroup<User> = new FormGroup<User>({
    name: new FormControl('', {nonNullable: true}),
    email: new FormControl('', {nonNullable: true}),
    isAdmin: new FormControl(false, {nonNullable: true}),
});
Note that not specifying the nonNullable attribute may lead to errors in your template regarding values being null, especially when binding form control values.

Angular now automatically checks the type system even if you don't explicitly define the type, as demonstrated here:

const cat = new FormGroup({
   name: new FormGroup({
      first: new FormControl('Barb'),
      last: new FormControl('Smith'),
   }),
   lives: new FormControl(9),
});

// Type-checking for forms values!
// TS Error: Property 'substring' does not exist on type 'number'.
let remainingLives = cat.value.lives.substring(1);

// Optional and required controls are enforced!
// TS Error: No overload matches this call.
cat.removeControl('lives');

// FormGroups are aware of their child controls.
// name.middle is never on cat
let catMiddleName = cat.get('name.middle');

For more information, check out the Angular 14 release update on their blog here

Answer №4

forms.ts

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

export type ModelFormGroup<T> = FormGroup<{
  [K in keyof T]: FormControl<T[K] | null>;
}>;

login.ts

export interface Ilogin {
  username: string;
  password: string;
}

login.component.ts

loginForm!: ModelFormGroup<Ilogin>;

constructor() {
    this.loginForm = new FormGroup({
      username: new FormControl<string | null>(null, Validators.required),
      password: new FormControl<string | null>(null, Validators.required),
    });
}

Answer №5

Dealing with a similar issue led me to come up with this solution focusing on the type of value within the form rather than the form itself:

export interface UserFormValue {
  first_name: string
  last_name: string
  referral: string
  email: string
  password: string
}
...

ngOnInit() {
  this.userForm = this.fb.group({
    first_name: [ '', Validators.required ],
    last_name: [ '', Validators.required ],
    referral: [ '' ],
    email: [ '', [ Validators.required, Validators.email ] ],
    password: [ '', [ Validators.required, Validators.minLength(8) ] ],
  });
}

...

In the template, submitting the value becomes simpler:

<form [formGroup]="userForm" (ngSubmit)="onSubmit(userForm.value)">
   ...
</form>

Now, adding a type to the submit function enhances clarity:

onSubmit(userForm: UserFormValue) {
   ...
}

While not perfect, this approach has proven effective for my needs. I do wish there was an option like this available:

userForm: FormGroup<UserFormValue>

Answer №6

Exciting news! Angular 14 (currently on the next channel) now supports strictly typed forms!

The FormGroup and FormArray classes are now generic, allowing for type safety within inner controls. The FormControl class also accepts generics for its value type. Additionally, a new class called FormRecord has been introduced for handling dynamic groups of controls.

Check out this example:

const party = new FormGroup({
  address: new FormGroup({
    house: new FormControl(123, {initialValueIsDefault: true}),
    street: new FormControl('Powell St', {initialValueIsDefault: true}),
  }),
  formal: new FormControl(true),
  foodOptions: new FormArray([
    new FormControl('Soup'),
  ])
});

// whichHouse is now strongly typed as `number`
const whichHouse = party.get('address.house')!.value;

// Error: control "music" does not exist
const band = party.controls.music;

Answer №7

Having nested type of groups allows you to handle scenarios like the following:
**models.ts

export type TCreateUserFields = {
    first_name: string,
    last_name: string,
    accept_terms: boolean,
};
export type TPasswordsControls = {
    passwords: FormGroup & {
        password: AbstractControl,
        confirm_password: AbstractControl
    }
}
export type TPasswordsFields = {
    passwords: {
        password: string,
        confirm_password: string
    }
}
export type TAllFields = TCreateUserFields & TPasswordsFields;
export type TAllControls = TCreateUserControls & TPasswordsControls;
export type TCreateUserControls = {
    [key in keyof TCreateUserFields]: AbstractControl
};
export type TCreateUserFormGroup = FormGroup & {value: TAllFields, controls: TAllControls};

**component.ts
this.registerationForm = this.fb.group(
{
    first_name: new FormControl("", [Validators.required]),
    last_name: new FormControl("", [Validators.required]),
    accept_terms: new FormControl(false, [Validators.required]),
    passwords: new FormGroup(
        {
            password: new FormControl("", [Validators.required, Validators.pattern(/^[~`!@#$%^&*()_+=[\]\{}|;':",.\/<>?a-zA-Z0-9-]+$/)]),
            confirm_password: new FormControl("", [Validators.required, Validators.pattern(/^[~`!@#$%^&*()_+=[\]\{}|;':",.\/<>?a-zA-Z0-9-]+$/)]),
        }, { 
            validators: <ValidatorFn>pwdConfirming({key:'password', confirmationKey:'confirm_password'})
        }
    )
} as TCreateUserControls) as TCreateUserFormGroup;

Answer №8

I found a fantastic solution for my project in the form of a library called ngx-strongly-typed-forms. This library offers strongly typed FormControls, FormGroups, and FormArrays which have greatly improved my workflow. While there are some limitations, it has proven to be incredibly helpful.

If you're interested in learning more, check out the documentation at https://github.com/no0x9d/ngx-strongly-typed-forms

Answer №9

In the latest version of Angular, reactive forms have now been made strictly typed by default.

https://angular.io/guide/typed-forms

interface RegistrationForm {
    username: FormControl<string>;
    email: FormControl<string>;
}

const registration = new FormGroup<RegistrationForm>({
    username: new FormControl('', {nonNullable: true}),
    email: new FormControl('', {nonNullable: true}),
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating computed properties in Vuejs using Typescript is a powerful feature that can enhance the functionality

Whenever I open my browser, I encounter the following message: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being ...

What is the best way to upload mp3 files using Angular?

Hello, I am a beginner with Angular and I could use some guidance. I am looking to upload mp3 files from my Angular application and then send them to the backend to be saved in my local database. Any tips or suggestions on how I can achieve this would be ...

Return a potential undefined output

I am working with a variable called root which could potentially be undefined. Its value is only determined at runtime. const root = resolvedRoot || await this.fileSystem.getCurrentUserHome(); console.log('root.uri = ' + root.uri); The existenc ...

Merge various observables into a unified RxJS stream

It seems that I need guidance on which RxJS operator to use in order to solve the following issue: In my music application, there is a submission page (similar to a music album). To retrieve the submission data, I am using the query below: this.submissio ...

Is there a way to make an angular component reuse itself within its own code?

I am dealing with a parent and child component scenario where I need to pass data from the parent component to the child component. The aim is to display parentData.name in the child component when parentData.isFolder===false, and if parentData.isFolder=== ...

Is there a way to prevent Angular component lifecycle hooks like ngOnInit/ngOnDestroy from being triggered when nested within one another?

I am currently developing an application with a page structure that resembles the following: Displaying Objects retrieved from Firestore (similar to Instagram posts) Loading Comments (using the object's id to display comments sub-collection) Load ...

Unable to delete event listeners from the browser's Document Object Model

Issue at hand involves two methods; one for initializing event listeners and the other for deleting them. Upon deletion, successful messages in the console confirm removal from the component's listener array. However, post-deletion, interactions with ...

Guide for activating the configuration options in my Visual Studio Code extension (vscode.workspace.getConfiguration())

I am currently developing a custom Visual Studio Code extension and I am looking to implement configuration settings for users to personalize the extension according to their preferences. https://i.sstatic.net/6Qkak.png Initially, I have included these li ...

Customizing Button Colors in Angular Material Design

I am looking to incorporate custom colors, much like the existing "primary" and "warn" options. For instance, I have added a class for an orange background, but I am using it as a class rather than a color attribute. While it is functional, the code appea ...

NG2-Charts are not rendering until the page is manually refreshed

I am currently working on an Angular project utilizing Angular 11. The issue I am encountering pertains to ng2-charts. Essentially, the chart is populated with data from an api-request call to the backend. I have identified where the problem lies, but I ...

The layout of the RadDataForm with nested groups in a GridLayout using NativeScript Angular does not properly display all fields on iOS

In the Angular NativeScript (6.3.0) component, I am facing an issue with XML where the form group expands and shrinks correctly on Android when the group title is tapped. However, on iOS, the group does not expand when using GridLayout rows="auto" and doe ...

Discovering the Angular HostBinding feature with parameter capabilities

Within my component, there is a numeric input that determines the width of the component. I need to dynamically set the width based on this input value using classes, as the width calculation relies on Sass variables that may change over time. I have thre ...

"Techniques for incorporating a screen in Angular application (Switching between Edit and View modes) upon user interaction

We are currently working on a screen that requires the following development: This screen will have the following features: When clicking on a button, the fields should become editable. In the image provided, there is some repeated data, but in our case, ...

What is the best way to perform unit testing on a typescript function within a NestJS application using Jest

Currently, I am in the process of writing unit tests for functions within our API. However, since I am not very experienced in writing tests, I am unsure of how to effectively test a specific function using jest. The function in question is outlined below: ...

Bidirectional synchronization with Angular 6 reactive forms

I'm currently working on building a complex reactive form that includes nested components populated with data objects. The functionality I'm aiming for is similar to the two-way data binding in template-driven forms: when a user modifies an inpu ...

Here's how you can retrieve URL parameters in NextJs, such as `userid/:this_is_a_param`

I'm struggling to retrieve URL parameters using Next.js. I normally do this with Express by getting a :param from the URL like this: users/:userid/ console.log(req.params.userid) All I've been able to do is get the "userid" from the URL like thi ...

"Encountering a Bug in Angular 2 Related to Chart.js

I am currently in the process of developing a Twitter-style application using a Typescript/Angular2 front-end framework and a Node.js back-end. The foundation of my project is derived from Levi Botelho's Angular 2 Projects video tutorial, although I h ...

What is the best method to adjust the width of the PrimeNG ConfirmDialog widget from a logic perspective

Currently utilizing "primeng": "^11.2.0" and implementing the ConfirmDialog code below this.confirm.confirm({ header: 'Announcement', message: this.userCompany.announcement.promptMsg, acceptLabel: this.userCompany.announcement ...

Guide on utilizing *ngFor with a JSON array in order to extract specific values

I am trying to display my JSON data where the "4th January 2018" is set as the list header and the description as the content of the list. How can I utilize the *ngFor directive for this task? However, when I attempt to use *ngFor="let item of timeline da ...

What could be the reason for being unable to execute the 'ng' command even after a smooth

I have successfully installed Angular CLI 1.5 using the following command: C:\ANGULAR-WORKBENCH>npm install --global @angular/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a5c6c9cce5948b908b95">[email protect ...