DEV Community

Renuka Patil
Renuka Patil

Posted on

4.Angular forms: Template-driven form vs Reactive forms

💡 There are two approaches to handle user’s input

  1. Template- driven form → html template
  2. Reactive forms ( Model driven form) → component

1.Template-driven form

  • If you want to add simple form (login, contact , signup)
  • Template-driven forms not scalable, expandable, upgradable
  • Very basic logic
  • Easily managed in html template
  • Can be created using directives → ngForm, ngModel
  • Basic validation can be used to validate form → required, maxlength, pattern
  • Custom validation , directives can be used
  • Less-explicit (automate)
  • Works *Asynchronously *→ page does not reload
  • Two-way data binding used

2.Reactive forms

  • More robust
  • Created in component class
  • Scalable, reusable, testable
  • Most prefferd to use if forms are key part of your application
  • More explicit(manual work)
  • Synchronous
  • Code-driven
  • Eliminate the anti-pattern of updating the data model via Two-Way data binding

1.Template-driven form

import FormsModule in app.module.ts

@NgModule({

 imports: [
    FormsModule
  ],

})
Enter fullscreen mode Exit fullscreen mode

Html file:


<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="bg-primary text-center p-3">
                <h2>Template driven form</h2>
            </div><br><br>
            <form #newForm="ngForm" (ngSubmit)="save(newForm.value)" >
                <div class="form-group">
                    <label for="">Enter Name: </label>&nbsp;<br>
                    <input type="text" name="name" ngModel class="form-control" placeholder="Enter Name">
                </div><br>

                <div class="form-group">
                    <label for="">Enter Age: </label>&nbsp;<br>
                    <input type="number" name="age" ngModel class="form-control" placeholder="Enter Age">
                </div><br>

                <div class="form-group">
                    <label for="">Enter email: </label>&nbsp;<br>
                    <input type="email" name="email" ngModel class="form-control" placeholder="Enter Email" >
                </div><br>

                <div class="d-grid">
                    <input type="submit" value="submit" class="btn btn-primary">
                </div>
            </form>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

note: ngModel → two way data binding

newForm → template reference variable

component file

  save(formData: any){
    console.log(formData);
  }
Enter fullscreen mode Exit fullscreen mode

Output:

Image description


Lets try to use json pipe and show the results in html template file: {{ newForm.value | json}}


<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="bg-primary text-center p-3">
                <h2>Template driven form</h2>
            </div><br><br>
            <form #newForm="ngForm" (ngSubmit)="save(newForm.value)" >
                            {{ newForm.value | json}}
                <div class="form-group">
                    <label for="">Enter Name: </label>&nbsp;<br>
                    <input type="text" name="name" ngModel class="form-control" placeholder="Enter Name">
                </div><br>

                <div class="form-group">
                    <label for="">Enter Age: </label>&nbsp;<br>
                    <input type="number" name="age" ngModel class="form-control" placeholder="Enter Age">
                </div><br>

                <div class="form-group">
                    <label for="">Enter email: </label>&nbsp;<br>
                    <input type="email" name="email" ngModel class="form-control" placeholder="Enter Email" >
                </div><br>

                <div class="d-grid">
                    <input type="submit" value="submit" class="btn btn-primary">
                </div>
            </form>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Image description


Binding form data to model - two way data binding

student.ts→ model class

    export class Student {
           constructor(
        public name?: string,
        public age?: number,
        public email?: string
    ) {}

    }
Enter fullscreen mode Exit fullscreen mode

note: ? used to make nullable

component file

save(formData: any){
    const std = new Student(formData.name, formData.age, formData.email);
  }
Enter fullscreen mode Exit fullscreen mode

two-way data binding → [(ngModel)]

Html file


<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="bg-primary text-center p-3">
                <h2>Template driven form</h2>
            </div><br><br>
            <form #newForm="ngForm" (ngSubmit)="save(newForm.value)" >
                {{ newForm.value | json}}
                <div class="form-group">
                    <label for="">Enter Name: </label>&nbsp;<br>
                    <input type="text" name="name" [(ngModel)]="std.name" class="form-control" placeholder="Enter Name" >
                </div><br>

                <div class="form-group">
                    <label for="">Enter Age: </label>&nbsp;<br>
                    <input type="number" name="age" [(ngModel)]="std.age" class="form-control" placeholder="Enter Age" >
                </div><br>

                <div class="form-group">
                    <label for="">Enter email: </label>&nbsp;<br>
                    <input type="email" name="email" [(ngModel)]="std.email" class="form-control" placeholder="Enter Email" >
                </div><br>

                <div class="d-grid">
                    <input type="submit" value="submit" class="btn btn-primary">
                </div>
            </form>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Component file

std = new Student("", 0, "");

  save(formData: any){
    console.log(this.std);
  }
Enter fullscreen mode Exit fullscreen mode

Form validation

  • Provided by validation directives
  • In built validators
  • Novalidate attribute on element → to disable browser validation
  • Useful properties: touched untouched valid invalid dirty pristine

<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="bg-primary text-center p-3">
                <h2>Template driven form</h2>
            </div><br><br>
            <form #newForm="ngForm" (ngSubmit)="save(newForm.value)" novalidate>
                {{ newForm.value | json}}
                <div class="form-group">
                    <label for="">Enter Name: </label>&nbsp;<br>
                    <input type="text" required #name="ngModel" name="name" [(ngModel)]="std.name" class="form-control" placeholder="Enter Name" >
                </div><br>
                touched: {{name.touched}}
                untouched: {{name.untouched}}
<br>
                valid: {{name.valid}}
                invalid: {{name.invalid}}
                <br>
                pristin: {{name.pristine}}
                dirty: {{name.dirty}}
                <br>

                <div class="form-group">
                    <label for="">Enter Age: </label>&nbsp;<br>
                    <input type="number" name="age" [(ngModel)]="std.age" class="form-control" placeholder="Enter Age" >
                </div><br>

                <div class="form-group">
                    <label for="">Enter email: </label>&nbsp;<br>
                    <input type="email" name="email" [(ngModel)]="std.email" class="form-control" placeholder="Enter Email" >
                </div><br>

                <div class="d-grid">
                    <input type="submit" value="submit" class="btn btn-primary">
                </div>
            </form>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Image description

Let's put validations on each field, validations only shows when field is touched and kept empty which violates our validations


<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="bg-primary text-center p-3">
                <h2>Template driven form</h2>
            </div><br><br>
            <form #newForm="ngForm" (ngSubmit)="save(newForm.value)" novalidate>
                <div class="form-group">
                    <label for="">Enter Name: </label>&nbsp;<br>
                    <input type="text" minlength="3" maxlength="15" required #name="ngModel" name="name" [class.is-invalid]="name.touched && name.invalid" [(ngModel)]="std.name" class="form-control" placeholder="Enter Name" >
                </div><br>
                <div class="alert alert-danger" *ngIf="name.touched && name.invalid">
                    <div *ngIf="name.errors && name.errors['required']">
                        name is required
                    </div>
                    <div *ngIf="name.errors && name.errors['minlength']">
                        3 characters must
                    </div>
                </div>


                <div class="form-group">
                    <label for="">Enter Age: </label>&nbsp;<br>
                    <input type="number" min="10" max="50"   #age="ngModel" [class.is-invalid]="age.touched && age.invalid" name="age" [(ngModel)]="std.age" class="form-control" placeholder="Enter Age" >
                </div><br>
                <div class="alert alert-danger" *ngIf="age.touched && age.invalid">
                    <div *ngIf="age.errors && age.errors['required']">
                        age is required
                    </div>
                    <div *ngIf="email.errors && email.errors['min']">
                        age must at least 10
                    </div>
                    <div *ngIf="email.errors && email.errors['max']">
                        age must at most 50
                    </div>
                </div>

                <div class="form-group">
                    <label for="">Enter email: </label>&nbsp;<br>
                    <input type="email" pattern="/^[a-zA-Z0-9. _-]+@[a-zA-Z0-9. -]+\. [a-zA-Z]{2,4}$/ " required #email="ngModel" [class.is-invalid]="email.touched && email.invalid" name="email" [(ngModel)]="std.email" class="form-control" placeholder="Enter Email" >
                </div><br>
                <div class="alert alert-danger" *ngIf="email.touched && email.invalid">
                    <div *ngIf="email.errors && email.errors['required']">
                        email is required
                    </div>
                    <div *ngIf="email.errors && email.errors['pattern']">
                        email is required
                    </div>

                </div>

                <div class="d-grid">
                    <input type="submit" [class.disabled]="newForm.form.invalid" value="submit" class="btn btn-primary">
                </div>
            </form>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Image description


Radio buttons and Dropdownlist

export class Student {
    constructor(
        public name?: string,
        public age?: number,
        public email?: string,
        public gender?: string,
        public country?: string
    ) {

    }
}
Enter fullscreen mode Exit fullscreen mode
 std = new Student("", 0, "");

  constructor() {
    this.std.country = "";

  }

  save(formData: any){
    console.log(this.std);
  }
Enter fullscreen mode Exit fullscreen mode
//radio                              
                             <label for="">Gender</label>
                <div class="form-check">
                    <input type="radio" required name="gender" #gender="ngModel" ngModel [(ngModel)]="std.gender" value="Male" class="form-check-input">
                    <label for="">Male</label>
                </div>
                <div class="form-check">
                    <input type="radio" required name="gender" #gender="ngModel" ngModel [(ngModel)]="std.gender" value="Female" class="form-check-input">
                    <label for="">Female</label>
                </div>

//dropdown
                            <div class="form-group">
                    <label for="">Country</label>
                    <select name="country" class="form-control" required="" [class.is-invalid]="country.touched && country.invalid"  #country="ngModel" ngModel [(ngModel)]="std.country" id="">
                        <option value="" selected>Select</option>
                        <option value="USA">USA</option>
                        <option value="Canada">Canada</option>
                        <option value="India">India</option>
                    </select>
                </div>
                <div class="alert alert-danger" *ngIf="country.touched && country.invalid">
                    <div *ngIf="country.errors && country.errors['required']">
                        country is required
                    </div>
                </div>

Enter fullscreen mode Exit fullscreen mode

Image description


Checkboxes

constructor(
        public name?: string,
        public age?: number,
        public email?: string,
        public gender?: string,
        public country?: string,
        public agree?: boolean,
        public hobby?: string
    ) { }
Enter fullscreen mode Exit fullscreen mode
 //single-select
               <div class="form-check">
                    <input type="checkbox" required name="agree" #agree="ngModel" ngModel [(ngModel)]="std.agree" class="form-check-input">
                <label for="">I accept terms and conditions</label>
                </div>

                <br>
                <label for="">Select Hobbies:  </label>


//multi-select
                <div class="form-check">
                    <input type="checkbox" name="Hobbies" ngModel  (change)="onChange($event)" value="Cricket" class="form-check-input">
                    <label for="">Cricket</label>
                </div>
                    <div class="form-check">
                    <input type="checkbox" name="Hobbies" ngModel (change)="onChange($event)"  value="Reading" class="form-check-input">
                    <label for="">Reading</label>
                </div>
                    <div class="form-check">
                    <input type="checkbox" name="Hobbies" ngModel (change)="onChange($event)" value="singing" class="form-check-input">
                    <label for="">singing</label>
                </div>
                <br>

                <div class="d-grid">
                    <input type="submit" [class.disabled]="newForm.form.invalid || selectedHobbies.length == 0" value="submit" class="btn btn-primary">
                </div>

Enter fullscreen mode Exit fullscreen mode

component file

selectedHobbies: string[]=[];

  std = new Student("", 0, "");
  constructor() {
    this.std.country = "";

  }

  save(formData: any){
    console.log(this.std);
    console.log(this.selectedHobbies);
  }

  onChange(event: any){
    let selected = event.target.value;
    let checked = event.target.checked;
    if(checked){
      this.selectedHobbies.push(selected);
    }else{
      let index = this.selectedHobbies.indexOf(selected);
      this.selectedHobbies.splice(index, 1);
    }

  }
Enter fullscreen mode Exit fullscreen mode

Image description


Reset Form

<button type="button" (click)="resetForm(newForm)" class="btn btn-danger">Reset</button>
Enter fullscreen mode Exit fullscreen mode
import {NgForm} from '@angular/forms'


resetForm(formData: NgForm){
    formData.reset();
    this.selectedHobbies = [];
  }


  save(formData: any){
    console.log(this.std);
    console.log(this.selectedHobbies);
    //formData.reset();
  }
Enter fullscreen mode Exit fullscreen mode

Image description


2. Reactive forms

  • Introduced in ANGULAR2
  • model-driven forms
  • no need of two way binding
  • form-group, form controls and form arrays
  • we also define validation rules
  • then we bind to html form
  • template → logic and controls defined in html form

advantages:

  • using custom validators
  • changing validation dynamically
  • dynamically adding form feilds

Import ReactiveFormsModule in app.module.ts

import {  ReactiveFormsModule } from '@angular/forms';
Enter fullscreen mode Exit fullscreen mode
<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="text-primary text-center">
                <h3>Reactive Form</h3>
            </div>
            <form [formGroup]="signupForm" (ngSubmit)="save()" action="">
                <div class="form-group">
                    <label for="">Enter Name</label>
                    <input type="text" formControlName="name" name="name" class="form-control">
                </div>

                <div class="form-group">
                    <label for="">Enter Age</label>
                    <input type="text" formControlName="age" name="age" class="form-control">
                </div>

                <div class="form-group">
                    <label for="">Enter Email</label>
                    <input type="text" formControlName="email" name="email" class="form-control">
                </div>
                <br>
                <div class="d-grid">
                    <input type="submit" value="Submit" class="btn btn-primary">
                </div>
            </form>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode
signupForm = new FormGroup({
    name: new FormControl(''),
    age: new FormControl(''),
    email: new FormControl(''),
  });

  save(){
    console.log(this.signupForm.value);
    }
Enter fullscreen mode Exit fullscreen mode

Image description


Form Validation

Getter function is only in reactive forms.

<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="text-primary text-center">
                <h3>Reactive Form</h3>
            </div>
            <form [formGroup]="signupForm" (ngSubmit)="save()" action="">
                <div class="form-group">
                    <label for="">Enter Name</label>
                    <input type="text" formControlName="name" [class.is-invalid]="f['name'].invalid && f['name'].touched" name="name" class="form-control">
                </div>
                <div class="alert alert-danger" *ngIf="f['name'].invalid && f['name'].touched">
                    <div *ngIf="f['name'].errors && f['name'].errors['required']">
                        Name is required
                    </div>
                    <div *ngIf="f['name'].errors && f['name'].errors['minlength']">
                        3 characters minimum
                    </div>
                </div>


                <div class="form-group">
                    <label for="">Enter Age</label>
                    <input type="text" formControlName="age" [class.is-invalid]="f['age'].invalid && f['age'].touched" name="age" class="form-control">
                </div>
                <div class="alert alert-danger" *ngIf="f['age'].invalid && f['age'].touched">
                    <div *ngIf="f['age'].errors && f['age'].errors['required']">
                        age is required
                    </div>
                    <div *ngIf="f['age'].errors && f['age'].errors['min']">
                        age at least 10
                    </div>
                    <div *ngIf="f['age'].errors && f['age'].errors['max']">
                        age at most 50
                    </div>
                 </div>

                <div class="form-group">
                    <label for="">Enter Email</label>
                    <input type="text" formControlName="email" [class.is-invalid]="f['email'].invalid && f['email'].touched" name="email" class="form-control">
                </div>
                <div class="alert alert-danger" *ngIf="f['email'].invalid && f['email'].touched">
                    <div *ngIf="f['email'].errors && f['email'].errors['required']">
                        email is required
                    </div>
                    <div *ngIf="f['email'].errors && f['email'].errors['email']">
                        invalid email
                    </div>
                </div>
                <br>
                <div class="d-grid">
                    <input type="submit" value="Submit" [class.disabled]="signupForm.invalid" class="btn btn-primary">
                </div>
            </form>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode
signupForm = new FormGroup({
    name: new FormControl('', [Validators.required, Validators.minLength(3)]),
    age: new FormControl('', [Validators.required, Validators.min(10), Validators.max(50)]),
    email: new FormControl('', [Validators.required, Validators.email]),
  });

  save(){
    console.log(this.signupForm.value);
  }

  get f(){
    return this.signupForm.controls;
  }
Enter fullscreen mode Exit fullscreen mode

Image description


Radion button and dropdown

component file

signupForm = new FormGroup({
    name: new FormControl('', [Validators.required, Validators.minLength(3)]),
    age: new FormControl('', [Validators.required, Validators.min(10), Validators.max(50)]),
    email: new FormControl('', [Validators.required, Validators.email]),
    gender: new FormControl('', [Validators.required]),
    country: new FormControl('', [Validators.required]),
  });
Enter fullscreen mode Exit fullscreen mode

html file

<label for="">Gender: </label>
                <div class="form-check">
                    <input type="radio" name="gender" value="Male" formControlName="gender" class="form-check-input" id="">
                    <label for="">Male</label>
                </div>
                <div class="form-check">
                    <input type="radio" name="gender" value="Female" formControlName="gender" class="form-check-input" id="">
                    <label for="">Female</label>
                </div>

                <div class="form-group">
                    <label for="">Country:</label>
                    <select name="country" formControlName="country" [class.is-invalid]="f['country'].invalid && f['country'].touched"  class="form-control" id="">
                        <option value="">Select</option>
                        <option value="India">India</option>
                        <option value="UK">UK</option>
                        <option value="USA">USA</option>
                    </select>
                </div>
                <div class="alert alert-danger" *ngIf="f['country'].invalid && f['country'].touched">
                    <div *ngIf="f['country'].errors && f['country'].errors['required']">
                        country is required
                    </div>
                </div>

Enter fullscreen mode Exit fullscreen mode

Image description

After filling the form:

Image description


Checkbox

hobbies: string[] = ['reading', 'writing', 'singing'];

  signupForm = new FormGroup({
    name: new FormControl('', [Validators.required, Validators.minLength(3)]),
    age: new FormControl('', [Validators.required, Validators.min(10), Validators.max(50)]),
    email: new FormControl('', [Validators.required, Validators.email]),
    gender: new FormControl('', [Validators.required]),
    country: new FormControl('', [Validators.required]),
    agree: new FormControl(false, [Validators.requiredTrue]),
    hobby: new FormArray([], [Validators.required]),
  });

  onChange(e:any){
    const check = e.target.value;
    const checked = e.target.checked;

    const checkedArray = this.signupForm.get('hobby') as FormArray;

    if(checked){
      checkedArray.push(new FormControl(check));
    }else{
      let i: number = 0;
      checkedArray.controls.forEach((item) => {
        if(item.value == check){
          checkedArray.removeAt(i);
        }
        i++;
      });
    }
  }

  save(){
    console.log(this.signupForm.value);
  }

  get f(){
    return this.signupForm.controls;
  }

Enter fullscreen mode Exit fullscreen mode
             <label for="">Hobbies</label>
                <div *ngFor="let hobby of hobbies; let i =index" class="">
                    <label for="">
                        <input type="checkbox" (change)="onChange($event)" class="form-check-input" [value]="hobby" name="hobby" id="">
                    {{hobby}}
                    </label>
                </div>

                <div class="form-check">
                    <input type="checkbox" name="gender" value="Female" formControlName="agree" class="form-check-input" id="">
                    <label for="">I accept terms and conditions</label>
                </div>
Enter fullscreen mode Exit fullscreen mode

Image description


You can see these blogs to cover all angular concepts:

Beginner's Roadmap: Your Guide to Starting with Angular

  1. Core Angular Concepts
  2. Services and Dependency Injection
  3. Routing and Navigation
  4. Forms in Angular
  5. RxJS and Observables
  6. State Management
  7. Performance Optimization

You can see these blogs to cover all angular concepts:

Beginner's Roadmap: Your Guide to Starting with Angular

  1. Core Angular Concepts
  2. Services and Dependency Injection
  3. Routing and Navigation
  4. Forms in Angular
  5. RxJS and Observables
  6. State Management
  7. Performance Optimization

Happy Coding!

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Hi Renuka Patil,
Very nice and helpful !
Thanks for sharing.