Angular 2 Forms

We will go through the following topics:

  • What is Angular 2 Forms all about
  • Template Driven Forms, or the Angular 1 way
  • Model Driven Forms, or the new Functional Reactive way
  • Updating Form Values
  • How To Reset a Form
  • Advantages and disadvantages of both form types
  • Can and should the two be used together ?
  • Which one to choose by default ?

Angular 2 Forms - what is it all about?

Many of frontend applications applications are basically just huge forms, spanning multiple tabs and dialogs and with non-trivial validation business logic.

Every form-intensive application has to provide answers for the following problems:

  • how to keep track of the global form state
  • know which parts of the form are valid and which are still invalid
  • properly displaying error messages to the user so that the user knows what to do to make the form valid

Template driven

  • Elements like ngModel, ngModelGroup, ngForm
  • implicitly created
  • asynchronous, lots of stuff going on behind the scenes for wiring up the FormControl instance etc.

Reactive, or model-driven

  • Elements like formControlName, formGroupName, formArrayName, formControl, formGroup
  • explicitly/programmatically created
  • synchronous and more predictive as they’re created programmatically and there’s not template rendering in the middle

Two different approaches

Enabling Template Driven Forms

import {NgModule} from "@angular/core";
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {BrowserModule} from "@angular/platform-browser";
import {FormsModule} from "@angular/forms";

@Component({...})
export class App { }

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

platformBrowserDynamic().bootstrapModule(AppModule);

Runnable Plunker

Our First Template Driven Form

<section class="sample-app-content">
    <h1>Template-driven Form Example:</h1>
    <form #f="ngForm" (ngSubmit)="onSubmitTemplateBased()">
        <p>
            <label>First Name:</label>
            <input type="text"  
                [(ngModel)]="user.firstName" required>
        </p>
        <p>
            <label>Password:</label>
            <input type="password"  
                [(ngModel)]="user.password" required>
        </p>
        <p>
            <button type="submit" [disabled]="!f.valid">Submit</button>
        </p>
    </form>
</section>

NgModel Validation Functionality

Angular is actually tracking three form field states for us and applying the following CSS classes to both the form and its controls:

  • touched or untouched
  • valid or invalid
  • pristine or dirty

These CSS state classes are very useful for styling form error states.

Controller

@Component({
    selector: "template-driven-form",
    templateUrl: 'template-driven-form.html'
})
export class TemplateDrivenForm {

    user: Object = {};

    onSubmitTemplateBased() {
        console.log(this.vm);
    }
}

Not much to see here! We only have a declaration for a view model object user, and an event handler used by ngSubmit.

How does Angular pull this off then?

The way that this works, is that there is a set of implicitly defined form directives that are being applied to the view. Angular will automatically apply a form-level directive to the form in a transparent way, creating a FormGroup and linking it to the form.

ngNoForm

required | maxlength

The presence of [(ngModel)] will also create a bidirectional binding between the form and the user model, so in the end there is not much more to do at the level of the controller.

Field initialization

<p>
    <label>First Name:</label>
    <input type="text"  [ngModel]="user.firstName" required>
</p>
<p>
    <label>Password:</label>
    <input type="password"  [ngModel]="user.password" required>
</p>
user = {
    firstName: 'John',
    password:  'test'
};

Validation

<p>
      <label>First Name:</label>
      <input type="text" name="firstName" required ngModel>
</p>
<p>
      <label>Password:</label>
      <input type="password" name="password" required ngModel>
</p>

Advantages and Disadvantages of Template Driven Forms

Disadvantages:

  • pretty hard to read rather quickly
  • complex cross-field validations will decreases readability
  • form validation logic cannot be unit tested

 

Advantages: simplicity

Functional programming point of view

Bi-directional binding is a solution that promotes mutability

 

Each form has a state that can be updated by many different interactions

Model Driven Or Reactive Forms

import {NgModule} from "@angular/core";
import {ReactiveFormsModule} from "@angular/forms";

@Component({...})
export class App { }

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

First Reactive Form

<section class="sample-app-content">
    <h1>Model-based Form Example:</h1>
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
        <p>
            <label>First Name:</label>
            <input type="text" formControlName="firstName">
        </p>
        <p>
            <label>Password:</label>
            <input type="password" formControlName="password">
        </p>
        <p>
            <button type="submit" [disabled]="!form.valid">Submit</button>
        </p>
    </form>
</section>

Controller

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

@Component({
    selector: "model-driven-form",
    templateUrl: 'model-driven-form.html'
})
export class ModelDrivenForm {
    form: FormGroup;
    
    firstName = new FormControl("", Validators.required);
    
    constructor(fb: FormBuilder) {
        this.form = fb.group({
            "firstName": this.firstName,
            "password":["", Validators.required]
        });
    }
    onSubmitModelBased() {
        console.log("model-based form submitted");
        console.log(this.form);
    }
}

ngModel?

Plunker

FormGroup and FormControl code

Plunker

FormBuilder code

Advantages and disadvantages of Model Driven Forms

We can now unit test the form validation logic

 

The FormGroup and
FormControl classes provide an API that allows us to build UIs using a completely different programming style known as Functional Reactive Programming.

Functional Reactive Programming


this.form.valueChanges
        .map((value) => {
            value.firstName = value.firstName.toUpperCase();
            return value;
        })
        .filter((value) => this.form.valid)
        .subscribe((value) => {
           console.log("Model Driven Form valid value: vm = ",JSON.stringify(value));
        });
        

Advantages of Functional Reactive Programming (FRP)

  • pre-save the form in the background at each valid state, or even invalid (for example storing the invalid value in a cookie for later use)
  • typical desktop features like undo/redo

How To Reset a Form

reset() {
    this.form.reset();
}

Model-Driven vs Template Driven: can they be mixed ?

If by some reason we would need to, we could mix and match the two ways of building forms, for example:

  • we can use ngModelto read the data, and use FormBuilder for the validations. we don't have to subscribe to the form or use RxJs if we don't wish to.
  • We can declare a control in the controller, and then reference it in the template to obtain its validity state

But in general its better to choose one of the two ways of doing forms, and using it consistently throughout the application.

Which form type to choose, and why ?

Are you migrating an Angular 1 application into Angular 2? That is the ideal scenario for using Template Driven Forms.

Or are you building a new application from scratch ? Reactive forms are a good default choice, because more complex validation logic is actually simpler to implement using them.

Summary

  • Angular 2 provides with us ways to build forms: Template Driven and Model Driven.
  • The Template Driven approach is very familiar to Angular 1 developers, and is ideal for easy migration of Angular 1 applications into Angular 2.
  • The Model Driven approach removes validation logic from the template, keeping the templates clean of validation logic. But also allows for a whole different way of building UIs that we can optionally use.
  • This is not an exclusive choice, but for a matter of consistency its better to choose one of the two approaches and use it everywhere in our application.

Related Links

Angular 2 Forms

By Pavel Nasovich

Angular 2 Forms

  • 2,833