AngularJS 2.0 From The Beginning - ngForm Part Two - Day Sixteen

I am here to continue the discussion around AngularJS 2.0. So far, we have discussed about data binding, input properties, output properties, pipes, viewchild, Directives, Services including routes and template based form binding, using ngForms in Angular 2.0. Now, in this article, I will discuss how to create model based form, using Angular form module in Angular 2.0. In case you did not have a look at the previous articles of this series, go through the links mentioned below.

In my previous article, I already discussed about the template driven form binding in Angular 2.0. Now, in this article, we will discuss about the Model Driven or Reactive Form binding in the Angular 2.0.

While using Directives in our templates, it gives us the power of rapid prototyping without too much boilerplate. We are restricted in what we can do. Reactive Forms on the other hand lets us define our form through code and gives us much more flexibility and control over the data validation.

Advantages of Model Driven Forms 

  • UNIT TESTABLE
    Since we have the form model defined in our code, we can unit test it.

  • LISTEN TO FORM AND CONTROLS CHANGES
    With Reactive Forms, we can listen to form or control the changes easily. Each form group or form control exposes few events, which we can subscribe to (e.g. statusChanges, valuesChanges, etc).

To begin, we must first ensure that we are working with the right Directives and the right classes in order to take advantage of procedural forms. For this, we need to ensure that ReactiveFormsModule was imported in Bootstrap phase of the Application module. This will give us an access to the components, Directives and providers like FormBuilder, FormGroup and FormControl.

FormControl

Note that the FormControl class is assigned to similarly named fields, both on this and in the FormBuilder#group({ }) method. This is mostly for ease of access. By saving the references to the FormControl instances on this, you can access the inputs in the template without having to reference the form itself. The form fields can otherwise be reached in the template by using loginForm.controls.username and loginForm.controls.password. Likewise, any instance of FormControl in this situation can access its parent group by using its .root property (e.g. username.root.controls.password). A FormControl requires two properties: an initial value and a list of validators. Right now, we have no validation.

Validating Reactive Forms

Building from the previous login form, we can quickly and easily add the validation. Angular provides many validators out of the box. They can be imported along with the rest of dependencies for the procedural forms. We are using .valid and .untouched to determine, if we need to show the errors - while the field is required, there is no reason to tell the user that the value is wrong, if the field hasn't been visited yet. For built-in validation, we are calling .hasError() on the form element and we are passing a string, which represents the validator function , which we included. The error message only displays, if this test returns true.

Reactive Forms Custom Validation

As useful as the built-in validators are, it is very useful to be able to include your own. Angular allows you to do just that with minimal effort. A simple function takes the FormControl instance and returns null, if everything is fine. If the test fails, it returns an object with an arbitrarily named property. The property name is what will be used for the .hasError() test. 
  1. <div [hidden]="!password.hasError('needsExclamation')">  
  2.   Your password must have an exclamation mark!  
  3. </div> 
Now, demonstrate this concept and write the code given below.
 
app.component.homepage.html
  1. <h2>Model Driven Form</h2>  
  2. <div>  
  3.     <form [formGroup]="loginForm" (ngSubmit)="registerUser()">  
  4.         <table style="width:60%;" cellpadding="5" cellspacing="5">  
  5.             <tr>  
  6.                 <td style="width :40%;">  
  7.                     <label for="username">User Name</label>  
  8.                 </td>  
  9.                 <td style="width :60%;">  
  10.                     <input type="text" name="username" id="username" [formControl]="username">  
  11.                     <div [hidden]="username.valid || username.untouched">  
  12.                         <div>  
  13.                             The following problems have been found with the username:  
  14.                         </div>  
  15.                         <div [hidden]="!username.hasError('minlength')">  
  16.                             Username can not be shorter than 5 characters.  
  17.                         </div>  
  18.                         <div [hidden]="!username.hasError('required')">  
  19.                             Username is required.  
  20.                         </div>  
  21.                     </div>  
  22.                 </td>  
  23.             </tr>  
  24.             <tr>  
  25.                 <td style="width :40%;">  
  26.                     <label for="password">Password</label>  
  27.                 </td>  
  28.                 <td style="width :60%;">  
  29.                     <input type="password" name="password" id="password" [formControl]="password">  
  30.                     <div [hidden]="password.valid || password.untouched">  
  31.                         <div>  
  32.                             The following problems have been found with the password:  
  33.                         </div>  
  34.   
  35.                         <div [hidden]="!password.hasError('required')">  
  36.                             The password is required.  
  37.                         </div>  
  38.                         <div [hidden]="!password.hasError('needsExclamation')">  
  39.                             Your password must have an exclamation mark!  
  40.                         </div>  
  41.                     </div>  
  42.                 </td>  
  43.             </tr>  
  44.             <tr>  
  45.                 <td style="width :40%;"></td>  
  46.                 <td style="width :60%;">  
  47.                     <button type="submit" [disabled]="!loginForm.valid">Log In</button>  
  48.                 </td>  
  49.             </tr>  
  50.         </table>  
  51.     </form>  
  52.     <div *ngIf="showMessage">  
  53.         <h3>Thanks You {{formData.username}} for registration</h3>  
  54.     </div>  
  55. </div>   
app.component.homepage.ts
  1. import { Component, OnInit, ViewChild } from '@angular/core';  
  2. import { Validators, FormBuilder, FormControl, FormGroup  } from '@angular/forms';  
  3.   
  4. @Component({  
  5.     moduleId: module.id,  
  6.     selector: 'home-page',  
  7.     templateUrl: 'app.component.homepage.html'  
  8. })  
  9.   
  10. export class HomePageComponent implements OnInit {  
  11.   
  12.     private formData: any = {};  
  13.   
  14.     username = new FormControl('', [  
  15.         Validators.required,  
  16.         Validators.minLength(5)  
  17.     ]);  
  18.   
  19.     password = new FormControl('', [  
  20.         Validators.required,  
  21.         hasExclamationMark  
  22.     ]);  
  23.   
  24.     loginForm: FormGroup = this.builder.group({  
  25.         username: this.username,  
  26.         password: this.password  
  27.     });  
  28.   
  29.   
  30.     private showMessage: boolean = false;  
  31.   
  32.     constructor(private builder: FormBuilder) {  
  33.     }  
  34.   
  35.     ngOnInit(): void {  
  36.     }  
  37.   
  38.     registerUser() {  
  39.         thisthis.formData = this.loginForm.value;  
  40.         this.showMessage = true;  
  41.     }  
  42. }  
  43.   
  44. function hasExclamationMark(input: FormControl) {  
  45.     const hasExclamation = input.value.indexOf('!') >= 0;  
  46.   
  47.     return hasExclamation ? null : { needsExclamation: true };  
  48. }   
app.module.ts
  1. import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';  
  2. import { BrowserModule } from '@angular/platform-browser';  
  3. import { ReactiveFormsModule } from "@angular/forms";  
  4.   
  5. import { HomePageComponent } from './src/app.component.homepage';  
  6.   
  7. @NgModule({  
  8.     imports: [BrowserModule, ReactiveFormsModule],  
  9.     declarations: [HomePageComponent],  
  10.     bootstrap: [HomePageComponent]  
  11. })  
  12. export class AppModule { }   
index.html
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <title>Angular2 - Model Driven Form </title>  
  5.     <meta charset="UTF-8">  
  6.     <meta name="viewport" content="width=device-width, initial-scale=1">  
  7.     <link href="../resources/style/bootstrap.css" rel="stylesheet" />  
  8.     <link href="../resources/style/style1.css" rel="stylesheet" />  
  9.     <!-- Polyfill(s) for older browsers -->  
  10.     <script src="../resources/js/jquery-2.1.1.js"></script>  
  11.     <script src="../resources/js/bootstrap.js"></script>  
  12.   
  13.     <script src="../node_modules/core-js/client/shim.min.js"></script>  
  14.     <script src="../node_modules/zone.js/dist/zone.js"></script>  
  15.     <script src="../node_modules/reflect-metadata/Reflect.js"></script>  
  16.     <script src="../node_modules/systemjs/dist/system.src.js"></script>  
  17.     <script src="../systemjs.config.js"></script>  
  18.     <script>  
  19.         System.import('app').catch(function (err) { console.error(err); });  
  20.     </script>  
  21.     <!-- Set the base href, demo only! In your app: <base href="/"> -->  
  22.     <script>document.write('<base href="' + document.location + '" />');</script>  
  23. </head>  
  24. <body>  
  25.     <home-page>Loading</home-page>  
  26. </body>  
  27. </html>   
main.ts
  1. import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';  
  2.   
  3. import { AppModule } from './app.module';  
  4.   
  5. const platform = platformBrowserDynamic();  
  6. platform.bootstrapModule(AppModule); 
Now, run the code and the output is shown below.