Why You Should Use Strictly Typed Reactive Forms in Your Angular App
Tags: angular, forms, reactive
Published: 2022-12-30 06:02:51

I continue to play with the new features of Angular 14/15, and one pending task is to learn about Typed Reactive Forms. The strict forms help us avoid many common issues when working with our forms.

The best way to learn and understand why to use Typed Reactive forms is by showing a scenario. I continue with the project ['Using Functional Guards In Angular'.](https://www.danywalls.com/how-to-use-functional-router-guards-in-angular)

## Scenario

In our scenario, we need to add a new field in the `purchaseForm`, the field `amount`, and increase it to `0.5`, then submit the form.

```typescript
export class RegisterComponent  {

  purchaseForm = new FormGroup({
    name: new FormControl(''),
    email: new FormControl(''),
  });

  sendForm() {
    console.log(this.registerForm.value);
  }
}
```

## The Solution

First, add the amount `formControl` linked in the HTML Markup with input with `controlName`.

The code looks like this:

```typescript
  purchaseForm = new FormGroup({
    name: new FormControl(''),
    email: new FormControl(''),
    amount: new FormGroup('')
  });
```

HTML Markup

```html
<form [formGroup]="purchaseForm" (ngSubmit)="sendForm()">
  <label for="first-name">Name: </label>
  <input id="first-name" type="text" formControlName="name">
  <label for="email">Email</label>
  <input id="email" type="email" formControlName="email">
  <label for="amount">Amount</label>
  <input id="amount" type="number" formControlName="amount">
  *TAX 0.5 
  <button type="submit">Send</button>
</form>
```

## Add Taxes

Before sending the form, we need to increase the value because the amount is a string. We must convert it to a number with the `Number()` function.

Declare the variable `priceWithTax` to store the result of the operation.

```typescript
 const priceWithTax = Number(this.purchaseForm.controls.amount.value) + 0.5;
```

Next, using the get method, update the amount field using `get` and `patchValue`

```typescript
this.purchaseForm.get('amount')?.patchValue(priceWithTax)
```

But I got an error.

```bash
src/app/components/register/register.component.ts:17:5
    17     amount: new FormGroup('')
           ~~~~~~~~~~~~~~~~~~~~~~~~~
    The expected type comes from property 'amount', which is declared here on type 'Partial<{ name: string | null; email: string | null; amount: string; }>'
```

The error is because the `amount` field expects a string value, so parse the to string.

```typescript
this.purchaseForm.get('amount')?.patchValue(priceWithTax.toString())
```

It works, but the code is a bit fragile and unclear.

## Problems

We use the `get` method, passing a string to get the amount field it compiles but getting the error in runtime.

The PatchValue method is better because it proposes the available properties in the form.

```typescript
this.purchaseForm.patchValue({
      amount: priceWithTax.toString(),
})
```

Some questions come to my head.

*   Why do I need to convert the amount? It is a number :(
    
*   What happens if reset the form, the new value of the amount is null :(
    
*   How do I turn on my form more strictly?
    

Most of these problems do not exist anymore with Typed Forms.

## Convert To Typed Forms

The Reactive Type Forms give us better control and stricter template form checks. It helps complex forms and deeply nested control with type-safety API.

Let's move to convert my current form to the new reactive strict forms.

### FormControls

The FormControl support generic types. We can set the specific type for each field.

```plaintext
 purchaseForm = new FormGroup({
    name: new FormControl<string>(''),
    email: new FormControl<string>(''),
    amount: new FormControl<number>(0)
  });
```

So, the amount field is a number, so we don't need to convert the value to a number anymore.

In compilation, throw an error because it requires a number value.

```bash
Error: src/app/components/register/register.component.ts:25:7 - error TS2322: Type
 'string' is not assignable to type 'number'.

25       amount: priceWithTax.toString(),
         ~~~~~~
```

Remove the `toString()` and get another error in compilation because the amount should be null.

```bash
Error: src/app/components/register/register.component.ts:23:25 - error TS2531: Obj
ect is possibly 'null'.

23     const priceWithTax =this.purchaseForm.controls.amount.value + this.PURCHASE
_TAX;
```

Angular 14 provides a new property `nonNullable` option to tell the number not to be null.

```typescript
amount: new FormControl<number>(0, { nonNullable: true})
```

Perfect, we already move the controls to strict types next to FormGroup.

### FormGroup

The FormGroup supports generics types so that we can declare all fields required in my forms, like an interface extending from `FormGroup`.

```typescript
export interface PurchaseFormModel extends FormGroup<{
  name: FormControl<string>;
  email: FormControl<string>;
  amount: FormControl<number>;
}> {
}
```

Next, assign the interface type for the form.

```typescript
  purchaseForm!: PurchaseFormModel;
```

Finally, use the *formBuilder* to create the form for each property.

The form definition must match the interface and doesn't allow adding extra properties.

```typescript
 constructor(private fb: FormBuilder) {
    this.purchaseForm = this.fb.group(
      {
        name: this.fb.nonNullable.control('hello'),
        email: this.fb.nonNullable.control('demo@demo.com'),
        amount: this.fb.nonNullable.control(0),
      }
    )
  }
```

The form declaration requires all fields to be not null, so use them `this.fb.nonNullable.control` if we want to add a nullable field, like cookies. Add in the interface and set type string and null.

```typescript
export interface PurchaseFormModel extends FormGroup<{
  name: FormControl<string>;
  email: FormControl<string>;
  amount: FormControl<number>;
  cookies: FormControl<boolean | null>;
}> {
}
```

The field in the form builder uses control with the default value.

```typescript
 constructor(private fb: FormBuilder) {
    this.purchaseForm = this.fb.group(
      {
        name: this.fb.nonNullable.control('hello'),
        email: this.fb.nonNullable.control('demo@demo.com'),
        amount: this.fb.nonNullable.control(0),
        cookies: this.fb.control(true)
      }
    )
  }
```

Yeah, We have the form strict and matching with our interface.

### One More Thing

If we reset the form, it uses the default value in the form controls, not null, as before.

## Conclusion

It was a small slide about Type Reactive Forms in Angular 14/15, check out the code or read more about in the following links:

*   [Code Example GitHub](https://github.com/danywalls/functional-guards-angular/tree/typed-reactive-forms)
    
*   [Angular Typed Form Video](https://www.youtube.com/watch?v=L-odCf4MfJc)
    

One more thing:

## Do you want to learn to build complex forms and form controls quickly?

Go to learn the Advanced Angular Forms & Custom Form Control Masterclass by Decoded Frontend
[![Learn Advanced Angular Forms Build](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74hrbor45wiimg13hn7f.png)]
(https://courses.decodedfrontend.io/courses/advanced-angular-forms)


Photo by Markus Spiske on Unsplash