Angular Form Examples: Template Driven vs Reactive Forms

There are two ways to create forms in Angular. While the template driven approach is more familiar to AngularJS users, the reactive approach leverages observables and functional programming techniques.

While each has its pros/cons, the reality is you can use either approach to achieve the same end result.

Angular Form Example

Let's implement a basic form using Angular. We'll construct the same form using both the template driven and reactive approach so you can easily see the difference...

Importing the Modules

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

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

To use both template driven and reactive forms, we must first import the modules in the app.module.ts file:

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

While only the FormsModule is required for template driven forms, we must also import the ReactiveFormsModule for our reactive form example.

Don't forget to include these in the imports array in the @NgModule declaration as well.

The Data Model

user.ts

export class User {
  constructor(
    public name: string,
    public age: number
  ){}
}

Angular forms are designed around data models. We've created a User class to model a user in our application. For simplicity, a user has two defined fields for the user's name and age.

We create this class in it's own file user.ts and import it for use in our component classes...

The Template Driven Way...

app.component.ts

import { Component } from '@angular/core';
import { User } from './user';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  user = new User("", 0);

  submitForm(){
    console.log(this.user.name + " is " + this.user.age + " years old" );
  }
}

app.component.html

<form #myForm="ngForm" (ngSubmit) = "submitForm()">

  <label>Name:</label>
  <input type="text" [(ngModel)]="user.name" name="name" #nameField="ngModel" required />

  <label>Age:</label>
  <input type="text" [(ngModel)]="user.age"  name="age" #ageField="ngModel"  />

  <button type="submit" [disabled]="!myForm.valid">Submit</button>

</form>

<p>Name: {{user.name}}</p>
<p>Age: {{user.age}}</p>

<p style="color:red" [hidden] = "nameField.valid || !nameField.touched">Your name is required!</p>

Notice how we've created a simple form for entering a user's name and age. {{user.name}} and {{user.age}} display the dynamically updated values of the inputs. This is referred to as two-way data binding.

This two-way data binding is achieved through the [(ngModel)] directive. Notice how we set [(ngModel)]="user.name". This associates the input with the user variable we define in the AppComponent class.

[(ngModel)] requires the name attribute. This is why you see name="name" and name="age" defined on the inputs that use [(ngModel)].

Also notice how we've added template variables to both the <form> and input tags. While #myForm="ngForm" gives us a reference to the form as a whole, #nameField="ngModel"> and #ageField="ngModel" give us references to the inputs themselves.

This allows us to conditionally show / hide / disable things based on the state of the referenced element. For example, the [disabled]="!myForm.valid" makes the submit <button> disabled unless the form is valid.

And what makes the form valid? See the required attribute? Angular automagically wires this up so that #myForm is invalid if any of it's child elements are invalid.

We conditionally show an error message [hidden] = "nameField.valid || !nameField.touched" if the #nameField input is blank.

The Reactive Way

app.component.ts

import { Component } from '@angular/core';
import { User } from './user';
import { FormGroup, FormControl, Validators} from '@angular/forms'
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  user = new User("", 0);
  myForm = new FormGroup({
    name: new FormControl('', Validators.required),
    age: new FormControl('')
  })

  submitForm(){
    console.log(this.myForm.controls.name.value + " is " + this.myForm.controls.age.value + " years old" );
  }
}

app.component.html

<form [formGroup]="myForm" (ngSubmit) = "submitForm()">

  <label>Name:</label>
  <input type="text" formControlName="name" />

  <label>Age:</label>
  <input type="text" formControlName="age"  />

  <button type="submit" [disabled]="!myForm.valid">Submit</button>

</form>

<p>Name: {{myForm.controls.name.value}}</p>
<p>Age: {{myForm.controls.age.value}}</p>

<p style="color:red" [hidden] = "myForm.valid || !myForm.touched">Your name is required!</p>

The key difference between this example and the template driven form is the use of FormGroup, FormControl and Validators in the controller class. Notice how we first import these via:

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

Notice how we set a myForm variable to a new FormGroup instance. The FormGroup is an object of key/value pairs where the key is the field name and the value is a new FormControl() instance.

While this example adds a bit more complexity to app.component.ts, it makes for much cleaner markup in app.component.html. Since we define the form components in the controller, we don't have to define a bunch of template variables to implement all of the form validation and data binding in the view.

Instead, we add [fomrGroup]="myForm" to the <form> tag to associate our myForm controller variable with the template. We also add formControlName attributes to the <input /> fields to associate the FormControl instances in our controller with the template.

We can dynamically reference the input values without the use of ngModel. Instead, we simply reference the FormControl values like myForm.controls.name.value. This gives us the value of a given input at any point in time.

Template Driven vs Reactive Forms

Both examples achieve the same result. The template driven form even utilizes the same FormControl class under the hood. By autowiring these controls from the template and utilizing ngModel, the template driven approach is similar to AngularJS and more intuitive than the reactive approach.

While the reactive approach requires more implementation in the controller class, it's also less verbose on the template side. Reactive forms work with observables to create a more functional approach to form building. Using the FormBuilder class, reactive forms make dynamic form building easier. Since they don't rely on DOM elements, reactive elements are also easier to test.

Conclusion

Both reactive and template driven forms provide efficient ways for building forms in Angular. While the template driven syntax offers a more familiar approach, reactive forms offer a more dynamic way of building form groups and controls.

Your thoughts?