Tech Incent
Angular

Angular reactive form complete example: step-by-step

Reactive form

In this article, I will show angular reactive form examples with step-by-step explanations. The explanation contains FormGroup, FormControl, FormArray, form submission, a complete form example. So let’s start

What’s the angular reactive forms?

Reactive forms are form structures which define in the component class. Reactive forms are built with the model-driven approach to handling HTML form inputs values change over time. Reactive forms provide input validation in the component classes.

Some of the advantages of reactive forms:
  • Two way data binding
  • Dynamically adding form fields
  • Dynamically changing validation
  • Using¬†custom validators

How to use reactive forms?

  1. Import built-in ReactiveFormsModule module
  2. Define form model in component class using angular built-in forms blocks FormGroup, FormControl, FormArray
  3. Define the HTML form that resembles the form model.
  4. Bind model form in HTML with built-in formGroup attribute.
  5. Bind model forms controls in HTML with formControlName attribute.
  6. Define submit method in component class and declare in HTML form attribute (ngSubmit)

How to use reactive forms blocks?

FormGroup block

The FormGroup is a building block to define forms groups in the angular component class. FormGroup contains a collection of the child as FormControl, FormArray, and nested FormGroup. FormGroup takes the first argument collection of keys controls name which contain FormControl or FormArray or nested FormGroup. Example of FormGroup …

this.userForm = new FormGroup({
     // FormControll and FormArray come here
    })

Associate FormGroup into HTML form

Angular reactive forms provide formGroup an attribute to bind angular reactive form to template form

<form [formGroup]="userForm">
  ...
</form>

FormControl block

The FormControl is the built-in class that contains a value (initial state) as the first parameter, validations as the second parameter, and custom async validations as the third parameter. Each input of form should have bound each FormControl

this.userForm = new FormGroup({
  name: new FormControl(),
  email: new FormControl('', [Validators.required, Validators.email]),
  number: new FormControl('', Validators.required),
  password: new FormControl('', [Validators.required, Validators.minLength(6)])
})

Associate FormControl into HTML form

  <form [formGroup]="userForm">
    <input type="text" formControlName="name" placeholder="Name">
    <input type="email" formControlName="email" placeholder="email">
    <input type="number" formControlName="number" placeholder="number">
    <input type="password" formControlName="password" placeholder="password">
  </form>

FormArray block

The FormArray is a built-in block that manages the collection. The collection/FormArray block can contain FormControl, FormGroup, FormArray

this.userForm = new FormGroup({
  ...
  hobbies: new FormArray([
    new FormControl('', [Validators.required])
  ]),
  skills: new FormArray([
    new FormGroup({
      name: new FormControl(''),
      yearOfExperience: new FormControl(''),
    })
  ])
  ...
})

Associate FormArray into HTML form

Declare the FormArray controller property separately in the component class.

  get skillsControls(): any {
    return (<FormArray>this.userForm.get('skills')).controls;
  }

Now bind angular FormArray in the HTML template

 <div formArrayName="skills">
  <div *ngIf="skillsControls && skillsControls.length">
    <div *ngFor="let exception of skillsControls; let i = index" [formGroupName]="i">
        <input formControlName="name" type="text" placeholder="Name" />
        <input formControlName="yearOfExperience" type="text" placeholder="Year of Experiences" />
        <hr>
    </div>
  </div>
</div>

Access reactive form data

Angular reactive forms provide value property to access the value.

this.userForm.value // as form value
this.userForm.get('name').value // as specific control value of form
<formControl>.value // as name control value

access value via Observable

Angular reactive forms provide another methods valueChanges property which is an Observable that can trigger when form any fields value changes.

this.userForm.valueChanges.subscribe(data => {
  console.log('formData', data)
})

access with async pipe

{{userForm.valueChanges | async | json}}

How to submit reactive form?

To submit an angular form you need to define a submit method in the component class.

  onFormSubmit(): void {
    const formData = this.userForm.value
    console.log(formData)
    // Call api post service here
  }

Now emit the template form submit an event with the onFormSubmit method. Angular provides built-in template ngSubmit EventEmmeter to bind template form to submit with component submit method.

Note: Make sure your form has a button with type submit, which is trigger template form submit an event. it’s a good approach to submit a form.

<form [formGroup]="userForm" (ngSubmit)="onFormSubmit()">
   ...
  <button type="submit">Submit</button>
</form>

You can reactive form submit manually with button click event

<button type="button" (click)="onFormSubmit()">Submit</button>

What is angular FormBulder class?

The FormBuilder provides syntactic sugar that shortens creating instances of a FormControlFormGroup, or FormArray. It reduces the amount of boilerplate needed to build complex forms

Angular FormBuilder provides shortly creating instances of FormControl, FormGroup, FormArray, Which reduces repeated codes.

Checkout Angular FormBuilder Example:

Complete angular reactive form example step-by-step

For this example, I will create angular reactive userForm with row angular form method (FormGroup, FormControl, FormArray) and I will also create userForm with FormBuilder class

Step-1 Setting up new project

Generate new angular project

ng new angular-reactive-form --routing=true --style=scss --skip-tests
cd angular-reactive-form

This will generate a new angular project with routing, the style set to scss (available style CSS, SASS, SCSS, LESS), and skip test configuration.

Import ReactiveFormsModule modules

The reactive forms require the import ReactiveFormsModule module. Update app.module.ts file, add ReactiveFormsModule module.

import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

If you are using SharedModule import in ShareModule. it’s best to use and practice

Step-2 Building userForm in component class

We can build reactive form two ways

  1. Directly reactive form methods FormGroup, FormControl, FormArray.
  2. Build with FormBuilder class who is provide FormGroup, FormControl, FormArray method. Recommanded form complex form

Update app.component.ts file. define userForm instance of FormGroup.

Method-1 Create userForm with direct form method import

import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  userForm!: FormGroup;
  constructor() {}
  ngOnInit(): void {
    this.userForm = new FormGroup({
      name: new FormControl(),
      email: new FormControl('', [Validators.required, Validators.email]),
      number: new FormControl('', Validators.required),
      password: new FormControl('', [Validators.required, Validators.minLength(6)]),
      address: new FormGroup({
        street: new FormControl(''),
        city: new FormControl(''),
        zip: new FormControl('')
      }),
      skills: new FormArray([
        this.newSkillGroup()
      ])
    })
  }
  get skillsControls(): any {
    return (<FormArray>this.userForm.get('skills')).controls;
  }
  newSkillGroup(): FormGroup {
    return new FormGroup({
      name: new FormControl('', Validators.required),
      yearOfExperience: new FormControl('', Validators.required),
    });
  }
  addSkillGroup(): void {
    (<FormArray>this.userForm.get('skills')).push(this.newSkillGroup());
  }
  removeSkillGroup(index: number): void {
    (<FormArray>this.userForm.get('skills')).removeAt(index);
  }
  onFormSubmit(): void {
    const formData = this.userForm.value
    console.log(formData)
    // Call api post service here
  }
}

Method-2 create userForm with FormBuilder class

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  userForm!: FormGroup;
  constructor(private formBuilder: FormBuilder) {}
  ngOnInit(): void {
    this.userForm = this.formBuilder.group({
      name: [],
      email: ['', [Validators.required, Validators.email]],
      number: ['', Validators.required],
      password: ['', [Validators.required, Validators.minLength(6)]],
      address: this.formBuilder.group({
        street: [],
        city: [],
        zip: []
      }),
      skills: this.formBuilder.array([
        this.newSkillGroup()
      ])
    })
  }
  get skillsControls(): any {
    return (<FormArray>this.userForm.get('skills')).controls;
  }
  newSkillGroup(): FormGroup {
    return this.formBuilder.group({
      name: ['', Validators.required],
      yearOfExperience: ['', Validators.required]
    });
  }
  addSkillGroup(): void {
    (<FormArray>this.userForm.get('skills')).push(this.newSkillGroup());
  }
  removeSkillGroup(index: number): void {
    (<FormArray>this.userForm.get('skills')).removeAt(index);
  }
  onFormSubmit(): void {
    const formData = this.userForm.value
    console.log(formData)
    // Call api post service here
  }
}

The userForm contain four controls as name, email, number, password. Each controls provided the initial empty value. But email, number, and password is required validation provided. and contain “address” as another FormGroup, “skills” as FormArray

skills as FormArray

newSkillGroup contain skills form which has two instances of FormControl as name and yearOfExperience

In this component class defined skillsControls property which is an instance skills FormArray of userForm form. The skillsControls property helps to bind template skills form inputs with userForm skills control.

addSkillGroup and removeSkillGroup the method provides dynamically add and removes skills FormArray item.

Learn more about dynamic reactive form control

Submit form method

OnFormSubmit method responsible for the form to API

Step-3 Add userForm in component template

Update app.component.html file

<div class="container">
  <form [formGroup]="userForm" (ngSubmit)="onFormSubmit()">
    <h3>User Form</h3>
    <div class="input-group">
      <input type="text" formControlName="name" placeholder="Name">
    </div>
    <div class="input-group">
      <input [class.border-danger]="userForm.get('email')?.touched && userForm.get('email')?.invalid" type="email" formControlName="email" placeholder="email">
      <div *ngIf="userForm.get('email')?.touched && userForm.get('email')?.hasError('required')" class="text-sm text-danger">Email is required</div>
      <div *ngIf="userForm.get('email')?.touched && userForm.get('email')?.hasError('email')" class="text-sm text-danger">Email is not valid</div>
    </div>
    <div class="input-group">
      <input [class.border-danger]="userForm.get('number')?.touched && userForm.get('number')?.invalid" type="number" formControlName="number" placeholder="number">
      <div *ngIf="userForm.get('number')?.touched && userForm.get('number')?.hasError('required')" class="text-sm text-danger">Number is required</div>
    </div>
    <div class="input-group">
      <input [class.border-danger]="userForm.get('password')?.touched && userForm.get('password')?.invalid" type="password" formControlName="password" placeholder="password">
      <div *ngIf="userForm.get('password')?.touched && userForm.get('password')?.hasError('required')" class="text-sm text-danger">Password is required</div>
      <div *ngIf="userForm.get('password')?.touched && userForm.get('password')?.hasError('minlength')" class="text-sm text-danger">Password min 6 character required</div>
    </div>
    <hr>
    <h4>Address</h4>
    <div formGroupName="address">
      <div class="input-group">
        <input type="text" formControlName="street" placeholder="Street">
      </div>
      <div class="input-group">
        <input type="text" formControlName="city" placeholder="City">
      </div>
      <div class="input-group">
        <input type="text" formControlName="zip" placeholder="Zip Code">
      </div>
    </div>
    <hr>
    <h4>Skills</h4>
    <div formArrayName="skills">
      <div *ngIf="skillsControls && skillsControls.length">
        <div *ngFor="let skill of skillsControls; let i = index" [formGroupName]="i">
          <div class="input-group">
            <input [class.border-danger]="skill.get('name')?.touched && skill.get('name')?.invalid" formControlName="name" type="text" placeholder="Name" />
            <div *ngIf="skill.get('name')?.touched && skill.get('name')?.hasError('required')" class="text-sm text-danger">Skill Name is required</div>
          </div>
          <div class="input-group">
            <input [class.border-danger]="skill.get('yearOfExperience')?.touched && skill.get('yearOfExperience')?.invalid" formControlName="yearOfExperience" type="text" placeholder="Year of Experiences" />
            <div *ngIf="skill.get('yearOfExperience')?.touched && skill.get('yearOfExperience')?.hasError('required')" class="text-sm text-danger">Year of experiences required</div>
          </div>
          <div class="flex justify-end">
            <button (click)="removeSkillGroup(i)" type="button">remove</button>
          </div>
          <hr>
        </div>
      </div>
      <div class="flex justify-end">
        <button (click)="addSkillGroup()" type="button">
          Add new item
        </button>
      </div>
    </div>
    <button [disabled]="userForm.invalid" style="margin-top: 40px;" type="submit">Submit</button>
  </form>
</div>

Step-4 Add a few styles (optional)

Update app.component.scss file.

* {
  box-sizing: border-box;
}
.container {
  max-width: 400px;
  margin-left: auto;
  margin-right: auto;
  padding-top: 50px;
}
.text-sm {
  font-size: 80%;
  margin-top: 5px;
}
.border-danger {
  border-color: red;
}
.text-danger {
  color: red;
}

input {
  width: 100%;
  padding: 5px 12px;
}
.flex {
  display: flex;
}
.items-center {
  align-items: center;
}
.justify-end {
  justify-content: flex-end;
}
// form
.input-group {
  margin-bottom: 10px;
}

Related posts

Create forms using angular FormBulder

Sajal Mia

Explained RxJs switchMap operator with example

Sajal Mia

Angular Date as ago (minutes / hours / days / months / years ago) pipe

Sajal Mia