# Reactive Form

## About

{% embed url="<https://angular.dev/guide/forms/reactive-forms/>" %}

The Form is created programmatically and syncronized with the DOM.

Some advantages are:

* *It is build around Observable streams.*
* **It is more roboust, scalable, reusable and testable.**
* It uses synchronous data-flow between View and Data models, which makes creating large-scale forms easier.
* ***(View-to-Model)*** and ***(Model-to-view) flow.** (*[*more info*](https://angular.dev/guide/forms#data-flow-in-reactive-forms)*)*
* Keep the data model pure by providing it as an **immutable** data structure. So each time a change is triggered on the data model, the `FormControl` instance returns a new data model rather than updating the existing one.
  * This gives the ability to track unique changes to the data model through the control's Observable.
  * Then change detection is more efficient because it only needs to update on unique changes.
* **Can have Custom validation functions.**
* **Testing can be done without renderding the UI**, in these tests, status and data are queried and manipulated through the control without interacting the the change detection cycle. ([more info](https://angular.dev/guide/forms#testing-reactive-forms))

## Examples

### Strictly Typing Setup

You may strictly type the controls if needed with:

```typescript
@Component({ ... })
export class FormPageComponent {
    protected username = new FormControl<string | null>('');
}
```

### Simple Setup - Just `FormControl` without `Services`

By default `FormControl` create typed form controls based on the initial value.

*To use untyped form controls you must use `UntypedFormControl`*.

```typescript
@Component({
    selector: "app-form-page",
    import: [ReactiveFormsModule],
    template: ` <input type="text" [formControl]="username" /> `,
})
export class FormPageComponent {
    protected username = new FormControl("");

    protected resetUsername() {
        this.username.setValue("");
    }
}
```

### Simple Setup - `FormGroup` without `Services`

To Submit forms, use `ngSubmit` in the Template.

To programatically change form control values, you can use:

* `setValue`: Must update the hole `FormGroup` object, even if only updating one of the form controls.
* `patchValue`: Can update one or multiple form controls.

```typescript
@Component({
    seletor: "app-form-page",
    import: [ReactiveFormModule],
    template: `
        <form [formGroup]="loginForm" (ngSubmit)="handleSubmit()">
            <input type="text" formControlName="username" />
            <input type="password" formControlName="password" />
        </form>
    `,
})
export class FormPageComponent {
    protected loginForm = new FormGroup({
	username: new FormControl(""),
	password: new FormControl(""),
    });

    protected handleSubmit() { ... }

    protected resetForm() {
        this.loginForm.setValue({
            username: '',
            password: '',
        });

        // Or

        this.loginForm.patchValue({
            username: ''
        });
    }
}
```

### Nested Setup - `FormGroup` without `Services`

```typescript
@Component({
    seletor: "app-form-page",
    import: [ReactiveFormModule],
    template: `
        <form [formGroup]="loginForm" (ngSubmit)="handleSubmit()">
            <div formGroupName="user">
                <input type="text" formControlName="username" />
                <input type="password" formControlName="password" />
            </div>
            <div formGroupName="optional">
                <input type="number" formControlName="age" />
            </div>
        </form>
    `,
})
export class FormPageComponent {
    protected loginForm = new FormGroup({
        user = new FormGroup({
            username: new FormControl(""),
            password: new FormControl(""),
        }),
        optional = new FormGroup({
            age: new FormControl(0),
        }),
    });
}
```

### Simple Setup - `FormGroup` with `Service: FormBuilder`

{% embed url="<https://angular.dev/api/forms/FormBuilder>" %}
forms/form-builder
{% endembed %}

Controls created with `FormBuilder` will be **automatically typed** based on the initial value. *(To use untyped forms you must explicitly use the* `UntypedFormBuilder`*`)`*

`formBuilder` has 4 functions:

* `group()`: To create `FormGroup`.
  1. First parameter, a collection of child controls `FormControl`.
  2. Second Parameter, object of options `AbstractControlOptions` containing:
     * `validators`: Synchronous validators that will be ran for the Group. (For multiple controls)
     * `asyncValidators`: Aynsc validators that will be ran for the Group. (For multiple controls)
     * `updateOn`: The event upon which the control should be updated. (`'change' | 'blur' | 'submit'`)
* `record()`: To create `FormRecord`.
  1. First paremeter, a collection of child controls `FormControl`.
  2. Second Parameter, object of options `AbstractControlOptions` containing:
     * `validators`: Synchronous validators that will be ran for the Group. (For multiple controls)
     * `asyncValidators`: Aynsc validators that will be ran for the Group. (For multiple controls)
     * `updateOn`: The event upon which the control should be updated. (`'change' | 'blur' | 'submit'`)
* `control()`: To create `FormControl`.
  1. First parameter, the initial value for the control,
  2. Second parameter, `ValidatorFn` or `ValidatorFn[]` or `FormControlOptions`.
  3. Third parameter, `AsyncValidatorFn` or `AsyncValidatorFn[]`.
* `array()`: To create `FormArray`.
  1. First parameter, an Array of child Controls.
  2. Second parameter, `ValidatorFn` or `ValidatorFn[]` or `FormControlOptions`.
  3. Third parameter, `AsyncValidatorFn` or `AsyncValidatorFn[]`.

```typescript
@Component({
    seletor: "app-form-page",
    import: [ReactiveFormModule],
    template: `
        <form [formGroup]="loginForm" (ngSubmit)="handleSubmit()">
            <input type="text" formControlName="username" />
            <input type="password" formControlName="password" />
        </form>
    `,
})
export class FormPageComponent {
    private formBuilder = inject(FormBuilder);

    protected loginForm = this.formBuilder.group({
        username: [""],
        password: [""],
    });
}
```

### Validation Setup - `FormGroup`

{% embed url="<https://angular.dev/api/forms/Validators>" %}
forms/validators
{% endembed %}

Validators are passed as second parameter, after the initial value.

* Async Validators are passed as third parameter.

Form validation `status`, can be accessed by `formVariable.status`.

You can also access each form control status with:

* `formVariable.controls.<formControlName>.invalid`
* `formVariable.controls.get('formControlName').invalid`

```typescript
@Component({
    seletor: "app-form-page",
    import: [ReactiveFormModule],
    template: `
        <form [formGroup]="loginForm" (ngSubmit)="handleSubmit()">
            <input type="text" formControlName="username" />
            <input type="password" formControlName="password" />
        </form>
    `,
})
export class FormPageComponent {
    private formBuilder = inject(FormBuilder);

    protected loginForm = this.formBuilder.group({
        username: ["", Validators.required],
        password: ["", [Validators.required]],
    });

    // Or

    protected loginForm = this.formBuilder.group({
        username: ["", { validators: Validators.required }],
        password: ["", { validators: [Validators.required] }],
    });

    // Or

    protected loginForm = new FormGroup({
        username: new FormControl("", Validators.required),
        password: new FormControl("", [Validators.required]),
    });
}
```

### Dynamic Setup - `FormArray` with `Service: FormBuilder`

The `FormArray` instance represents an undefined number of controls in an array.

Each `FormControl` created will be referenced by its `index` in the `FormArray`.

It can be useful for creating dynamic form inputs or managing checkboxes or radio buttons.

```typescript
@Component({
    seletor: "app-form-page",
    import: [ReactiveFormModule],
    template: `
        <form [formGroup]="loginForm" (ngSubmit)="handleSubmit()">
            <input type="text" formControlName="username" />
            <input type="password" formControlName="password" />

            <button type="button" (click)="addAlias()">New Alias</button>
            <div>
                @for (alias of aliases; track alias.id; let idx = $index) {
                    <input type="text" [formControlName]="idx" />
                }
            </div>

            <!-- OR -->

            <div>
                @for (alias of formData.get('aliases').controls; track alias.id; let idx = $index) {
                    <input type="text" [formControlName]="idx" />
                }
            </div>
        </form>
    `,
})
export class FormPageComponent {
    private formBuilder = inject(FormBuilder);

    protected loginForm = this.formBuilder.group({
        username: ["", Validators.required],
        password: ["", [Validators.required]],
        aliases: this.formBuilder.array([this.formBuilder.control("")]),
    });

    // You may use getter syntax to create a class property to retrieve the Form Array control values from the group, instead of calling directly in the Template
    protected get aliases() {
        // By default the array control is return as AbstractControl, so explicitly type it as FormArray, so you can loop it in the Template
        return this.loginForm.get("aliases") as FormArray;

        // Or

        return this.loginForm.get("aliases").controls;
    }

    /*
        Define a method to dynamically insert new values into the created class property `aliases`.
    */
    protected addAlias() {
        this.aliases.push(this.formBuilder.control(""));

        // Or

        (<FormArray>this.formData.get("aliases")).push(this.formBuilder.control(""));
    }
}
```

### NonNullable Setup - `FormControl`

*When reseting the form, the `initial` values will be used by default.*

You may defined non-nullable form controls with:

```typescript
@Component({ ... })
export class FormPageComponent {
    protected username = new FormControl('', { nonNullable: true });
}
```

Or

```typescript
@Component({ ... })
export class FormPageComponent {
    private formBuilder = inject(NonNullableFormBuilder);

    // The entire form will be Non-Nullable
    protected loginForm = this.formBuilder.group({
        ...
    });
}
```

Or

```typescript
@Component({ ... })
export class FormPageComponent {
    private formBuilder = inject(FormBuilder);

    protected loginForm = this.formBuilder.group({
        username: ['', Validators.required],

        // You can define specific controls to be Non-Nullable
        password: this.formBuilder.nonNullable.control('', {
            validators: [Validators.required]
        })
        // or
        password: this.formBuilder.control('', {
            validators: [Validators.required],
            nonNullable: true,
        })
    });
}
```

### Subscribing to `statusChanges` or `valueChanges` Observables - Reactive value changes

For instance saving the form values in the `localStorage` after 500ms.

```typescript
@Component({ ... })
export class FormPageComponent implements OnInit {
    private formBuilder = inject(FormBuilder);
    private valueChangesSubscription;
    private statusChangesSubscription;

    protected loginForm = this.formBuilder.group({
        ...
    });

    ngOnInit() {
        const savedForm = window.localStorage.getItem('saved-form-data');

        if (savedForm) {
            const loadedForm = JSON.parse(savedForm);
            this.loginForm.patchValue({
                username: loadedForm.username
            });
        }

        // Executed everytime a value changes in the form
        this.valueChangesSubscription = this.loginForm.valueChanges.pipe(debouceTime(500)).subscribe((value) => {
            window.localStorage.setItem('saved-form-data', JSON.stringify({ username: value.username }));
        });

        // Executed everytime the form status changes
        this.statusChangesSubscription = this.loginForm.statusChanges.subscribe((status) => {
            console.log(status);
        });
    }

    ngOnDestroy() {
        this.valueChangesSubscription.unsubscribe();
        this.statusChangesSubscription.unsubscribe();
    }
}
```

### Custom Validators Setup - `Synchronous`

{% embed url="<https://angular.dev/guide/forms/form-validation#defining-custom-validators>" %}
forms/forms-validators#custom-validators
{% endembed %}

Functions that take a control instance and immediately return either a set of validation errors or null.

```typescript
@Component({ ... })
export class FormPageComponent {
    readonly formBuilder = inject(FormBuilder);

    private forbiddenUsernames = ['dicken', 'balls'];

    protected loginForm = this.formBuilder.group({
        username: new FormControl('', {
            // You must use 'bind' to pass the correct 'this' reference, which must be of the class
            validators: [Validators.required, this.checkNames.bind(this), this.checkRegExp(/bob/i)]
        })
    });
    
    // Or
    
    protected loginForm = this.formBuilder.group({
        // You must use 'bind' to pass the correct 'this' reference, which must be of the class
        username: ['', [Validators.required, this.checkNames.bind(this), this.checkRegExp(/bob/i)]]
    });

    // The return is a key, with an error flagname if invalid
    // Return nothing or null if it is valid
    checkNames(control: FormControl): {[key: string]: boolean} {
        if (this.forbiddenUsernames.indexOf(control.value) !== -1) {
            return { nameIsForbidden: true };
        }
        return null;
    }

    checkRegExp(re: RegExp): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
            const forbidden = re.test(control.value);
            return forbidden ? { forbiddenName: { value: control.value } } : null;
        }
    }
}
```

#### **Cross-field Validation**

*You add the validator method to the group.*

```typescript
@Component({ ... })
export class FormPageComponent {
    readonly formBuilder = inject(FormBuilder);

    protected loginForm = this.formBuilder.group({
        username: ['', Validators.required],
        passwords: this.formBuilder.group({
            password: [''],
            confirmPassword: ['']
        }, {
            validators: equalPasswords
        })
    });

    protected equalPasswords: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
        const password = control.get('password')?.value;
        const confirmPasswords = control.get('confirmPasswords')?.value;

        return password && confirmPasswords && password === confirmPasswords ? null : { notEqual: true };
    }
}
```

### Custom Validators Setup - `Async`

{% embed url="<https://angular.dev/guide/forms/form-validation#creating-asynchronous-validators>" %}
forms/forms-validators#async-validators
{% endembed %}

Functions that take a control instance and must return a `Promise` or `Observable` that later emits a set of validations errors or null.

**For performance reasons, Angular only runs async validators after all sync validators pass.**

The Validator functions takes the same `control: AbstractControl` param.

For intance reaching to a webserver to validate:

```typescript
@Component({ ... })
export class FormPageComponent {
    readonly formBuilder = inject(FormBuilder);

    protected loginForm = this.formBuilder.group({
        username: new FormControl('', {
            asyncValidators: [this.checkNames.bind(this)]
        })
    });
    // Or
    protected loginForm = this.formBuilder.group({
        username: ['', [], [this.checkNames.bind(this)]]
    });

    // The return is a key, with an error flagname if invalid
    // Return nothing or null if it is valid
    checkNames(control: FormControl) {
        // Will return a Promise of null or object
    }
}
```
