Click here to Skip to main content
15,886,110 members
Articles / Programming Languages / Javascript
Tip/Trick

Angular 2 Forms - Template Driven and Model Driven Approaches

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
26 Jan 2017CPOL8 min read 33.2K   4   1
A discussion about code, strategy and user experience

Introduction

In AngularJS (first framework version), we had a great development experience to build our forms, but this experience involves a lot of code in our HTML template and a high coupling between this template and our controller classes (and sometimes, with services too). In Angular 2, this experience was maintained, the two-way binding, validations states features, and so on. A lot of features and concerns were improved in Angular 2, like communication between components, code-reuse, separation of concerns, unit tests and others. To promote some of those benefits in context of forms, Angular 2 brought another option for build forms with a 'model-driven' approach using a reactive technique of development.

Background

For better understanding, along with this article, I'll show a sign up registration form built with both approaches, and discuss about situations when one should be more beneficial than the other.

The form that will be built in both approaches is it:

Image 1

This form has some required fields and a submit button which remains disabled while the form state is not valid. It will evolve along with this article in terms of user requirements and user experience to show the scenarios when Reactive Model will fit better than template-driven.

Template-driven' Form Code

To create a form using the template-driven approach, we can start with our form HTML element as follows:

HTML
<form class="form-horizontal"
      novalidate autocomplete="off"
      #signupForm="ngForm">
...
</form>

Notice the signupForm template variable which is a reference for the form itself (and its state, children elements and data) that will be used in this sample to improve the user experience by enabling the Submit button only when your state is valid:

HTML
<button class="btn btn-primary"
        (click)="save()"
        [disabled]="signupForm.invalid">
  Submit
</button>

And with the @ViewChild decorator, we can access it in our component as follows:

JavaScript
@ViewChild('signupForm') signupForm: NgForm;
...
save(): void {
  if (this.signupForm.valid) {
    ...
  }
}

An HTML template for email input can be written like so:

HTML
<div class="form-group"
     [ngClass]="{'has-error': 
     (email.touched || email.dirty) && email.invalid }">
  <label class="col-md-2 control-label">Email *</label>

  <div class="col-md-8">
    <input class="form-control"
           type="email"
           required
           pattern="[a-z0-9._%+-]+@[a-z0-9.-]+"
           [(ngModel)]="user.email"
           name="email"
           #email="ngModel"/>
    <span class="help-block" 
    *ngIf="(email.touched || email.dirty) && email.errors">
        <span *ngIf="email.errors?.required">
            Email is required.
        </span>
        <span *ngIf="email.errors?.pattern">
            Email format is invalid
        </span>
    </span>
  </div>
</div>

The code above is a common code related to work with forms inputs validation states and messages in Angular 2 at beginner level. In this sample, we show validation messages according to control´s status in runtime (when user is typing the value for the email input and we update the user.email model property simultaneously with the banana-in-the-box syntax [( )] used to provide two-way-binding behavior.

That´s it for template-driven, so simple syntax, so easy to work with, but in a simple scenario and with a good (not great) user experience.

Building Blocks

Behind the scenes, Angular2 creates a model object called FormGroup for us when working with template-driven approach, and its FormControl children for each form input like illustrated below:

Image 2

Speaking in terms of code, the Model-Driven approach (Reactive Forms) refers to strategy of building our own Model object (FormGroup and related items) and bind it on Component' HTML. It's implied in a more concise HTML Template and provides more control in our forms (but with more code on it).

The same FormGroup object which is automatically created and associated with the form can be created manually with ReactiveForms in our Component as follows:

JavaScript
this.signupForm = new FormGroup({
  firstName: new FormControl('', Validators.required),
  lastName: new FormControl('', Validators.required),
  email: new FormControl('', [Validators.required, Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+')]),
  password: new FormControl('', [Validators.required, Validators.minLength(6)]),
});

Note: With the FormBuilder service, this task can be made more easy. To check it, you can access the source sample at the end of this article.

Model-driven' Form Code

To demonstrate this approach, we will recreate the same form, for this task, we need to start by importing the ReactiveFormsModule (instead of FormsModule) from '@angular/forms' in our Module and in our Component, add a signupForm property of FormGroup type and initialize it as was shown in the previous section.

In the HTML template, we need to associate this property with our form element as follows:

HTML
<form class="form-horizontal"
      novalidate autocomplete="off"
     [formGroup]="signupForm">
...
</form>

To replicate the Submit button behavior as was made on the previous sample with template-syntax, this can be made with the same syntax, but this time, the reference to signupForm is not to a template variable, but is to a FormGroup root object.

The same email template that was previously shown can be made as follows:

HTML
<div class="form-group"
     [ngClass]="{'has-error': (signupForm.get('email').touched || 
     signupForm.get('email').dirty) 
     && signupForm.get('email').invalid }">
  <label class="col-md-2 control-label">Email *</label>

  <div class="col-md-8">
    <input class="form-control"
           type="email"
           name="email"
           formControlName="email"/>
    <span class="help-block" 
    *ngIf="(signupForm.get('email').touched || 
     signupForm.get('email').dirty) 
     && signupForm.get('email').errors">
        <span *ngIf="signupForm.get('email').errors?.required">
            Email is required.
        </span>
        <span *ngIf="signupForm.get('email').errors?.pattern">
            Email format is invalid
        </span>
    </span>
  </div>
</div>

We removed the required and pattern validation attributes (both were moved for the component class), the email template variable was replaced by formControlName and the [(ngModel)]="user.email" was removed (we do not have two-way binding anymore) and to access the value for the input in the save method, e.g., we need to do something like this.signupForm.get('firstName').value and to modify the form input via the component class, call the patchValue method:

JavaScript
this.signupForm.patchValue({
  email: 'richardleecba@gmail.com'
});

For more complex scenarios (business rules/reusable services), this can be a convenience in favor of immutability and better unit tested application...

When working with ReactiveForms, even with more code, the difference between these two code strategies is not soo big, the main difference resides in the loose of two-way-binding and the transference of HTML code to validation rules and other logics to our Component (some of these cases) will be exemplified later on in this article), doing it we have a Component better testable, a leverage of separation of concerns with HTML template with less logic code possible and more control over our forms, and with more control, more improvements can be made, and this is the point of the next section.

In the github repository, the final source code for both approaches is:

HTML
<div class="form-group"
     [ngClass]="{'has-error': 
     (email.touched || email.dirty) && email.invalid }">
  <label class="col-md-2 control-label">Email *</label>

  <div class="col-md-8">
    <input class="form-control"
           type="email"
           required
           pattern="[a-z0-9._%+-]+@[a-z0-9.-]+"
           [(ngModel)]="user.email"
           name="email"
           #email="ngModel"/>
    <span class="help-block" 
    *ngIf="(email.touched || email.dirty) && email.errors">
        <span *ngIf="email.errors?.required">
            Email is required.
        </span>
        <span *ngIf="email.errors?.pattern">
            Email format is invalid
        </span>
    </span>
  </div>
</div>
Reactive
<div class="form-group"
     [ngClass]="{'has-error': emailCurrentErrorMessage }">
  <label class="col-md-2 control-label">Email *</label>

  <div class="col-md-8">
    <input class="form-control"
           type="email"
           name="email"
           formControlName="email"/>
    <span class="help-block"
          *ngIf="emailCurrentErrorMessage">
        {{emailCurrentErrorMessage}}
    </span>
  </div>
</div>

Give Me More!

This sample form has a good user experience, but it does not mean that it can't be improved or even with a good UX in general, some input interaction with the user can be very boring, in this sample, the email input, e.g., while the user is typing the email value, the message of 'Email format is invalid' is shown to user, but while the user is typing of course, that it's invalid! To improve this, we could evaluate the value which the user typed after he finished typing.

To do this, we need to watch the user inputs and react to this input values changes, for this task, the FormControl object has a valueChanges property which returns an Observable that emits an event every time the value of the control changes, we can subscribe to it to react on this changes, e.g., adding a debounceTime operator to apply a delay of time after the user stops typing.

JavaScript
ngOnInit() {
  const emailControl = this.signupForm.get('email');
  emailControl.valueChanges
    .debounceTime(1000)
    .subscribe(() => this.setEmailMessages(emailControl));
}

Nowadays, a common good UX practice applied on user form registration is to add an input for email confirmation.

It´s not impossible to be made with template-syntax, but when working with ReactiveForms, this can be more easily made in a more natural way (without the need to create directives or use third-party components). It can be done in some simple steps (after confirmation input was added to the template like email was done):

Instead of initializing the FormGroup with email and confirmEmail as FormControls, we will group them together in a nested FormGroup inside the signupForm as follows:

JavaScript
this.signupForm = this.formBuilder.group({
  ...
    emailGroup: this.formBuilder.group({
      email: ['', [Validators.required, Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+')]],
      confirmEmail: ['', Validators.required],
  }, {validator: emailMatcher}),
  ...
});

And in the template, we create a div to group the email and confirmEmail HTML template (without any modification on its HTML) and a formGroupName attribute and set on it the FormGroup's nested element of the root signup form model object as follows:

HTML
<div formGroupName="emailGroup"
     [ngClass]="{'has-error': customerForm.get('emailGroup').errors }">
  <div class="form-group">
    ...Email syntax without modifications...
  </div>
  <div class="form-group">
    ...Confirm email syntax without modifications...
  </div>
</div>

Now, let´s handle a more complex scenario, when a new user requirement comes in to add a option to the user be kept in touch via your phone number or email, for this, we will add a phone input in the form and a radio to user check how we can maintain contact to him after his registration with email and phone options, email continues being required in the form, the phone is optional, but whether the user selects phone as the option to be contacted the phone becomes required too. in other words, we need to handle a runtime validation here.

For this task, we need to add the FormControl notification for the radio input and initialize it with a 'email' as default selected option in our signupForm initialization and subscribe to its valueChanges as follows:

JavaScript
this.signupForm.get('notification').valueChanges
                         .subscribe(value => this.setNotification(value));

The setNotification is the method which will change the Validator of a FormControl at runtime, according to the value passed to it:

JavaScript
setNotification(notifyVia: string): void {
  const phoneControl = this.customerForm.get('phone');
  if (notifyVia === 'phone') {
    phoneControl.setValidators(Validators.required);
  } else {
    phoneControl.clearValidators();
  }
  phoneControl.updateValueAndValidity();
}

In our template, we just need to add the input radio with the phone and email options, add the formControlName attribute and style it as you wish.

HTML
<div class="form-group" >
  <label class="col-md-2 control-label">Send Notifications</label>
  <div class="col-md-8">
    <label class="radio-inline">
      <input type="radio"
             value="email"
             formControlName = "notification">Email
    </label>
    <label class="radio-inline">
      <input type="radio"
             value="phone"
             formControlName = "notification">Phone
    </label>
  </div>
</div>

That´s it, working with ReactiveForms, this task becomes very simple and with a great user experience. By the way, watching and reacting to the user input changes in the form, gives to us a lot of control and possibilities to leverage the UX, some of them are shown here, but there are many other things yet, e.g., working with automatic suggestion, runtime user interface reactions, and more...

We can do even more yet, besides FormGroup and FormControl, we can work with another special control, the FormArray, which is used to dynamically duplicate elements in the UI, in scenarios in which the user may inform more than one address, like address for home, work and others.

Work with FormArray and iterate over it in our Component or in the HTML template is simple, but for not making this article very extensive, I will put this and all other samples in the github repository at the end of this article.

Summary

We change for template-driven approach which is more easy to work with it, to reactive (in model-driven approach) for giving us more control, that results in a better testable form by leveraging a decoupling between the HTML (design/CSS team can work here) and component's business rules (angular/js specialist members) and to improve the user experience with features like reactive transformations, correlated validations and handle complex scenarios as runtime validation rules and dynamic fields duplication.

This repository contains the source code used for the article

The source code shown in this article is available in a repository Github and can be seen at the following link:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Brazil Brazil
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questioncould not run Pin
Larry @Datasmith12-Feb-17 15:12
Larry @Datasmith12-Feb-17 15:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.