项目作者: dinanathsj29

项目描述 :
Learn Angular's dynamic Reactive Forms (Model Driven) step-by-step with live example.
高级语言: TypeScript
项目地址: git://github.com/dinanathsj29/angular-forms-reactivemodeldriven-tutorial.git



Angular logo

Angular Reactive Forms (Model driven)

Working with existing/cloned/copied Angular App

  • Clone or Download the project/app from Github or any other sources
  • If using Visual Studio Code / Insiders, open Command panel/terminal from menu: View -> Terminal (shortcut key is CTRL + BackTick OR COMMAND + J)
  • Go inside the project/app directory, command: cd _examples-angular-templateDrivenForm OR cd templateDrivenForm
  • Run command: npm install to install project/app dependencies (node_modules)
  • To Build and run Angular App, command: ng serve / npm start OR ng serve -o OR ng serve --open
  • To change the port from 4200 to other port - type command: ng serve --port 5000
  • To check the application in browser type path/URL: localhost:4200 / 5000

1 - Introduction to Reactive Forms (Model-driven)

1.1 Reactive Model Driven Forms - what is it all about?

  • Angular reactive forms, also known as model-driven forms, offers an easy way to use reactive programming styles-patterns and validations
  • Reactive forms are forms where we write logic, validations, controls in the component’s class
  • It is flexible and can be used to handle complex form scenarios and large forms
  • We write more component code and less HTML code which make unit testing easier

1.2. Some important points about Reactive Forms (Model Driven)

  • Code and Logic resides in the component class (Template Driven Forms focus mainly on HTML template)
  • No Two Way Data Binding
    • (we need to react to user inputs to update the values, also some inbuilt angular methods are available to update component class)
  • Reactive forms are mainly used/well suited for complex scenarios:
    • Dynamic (On the Fly creation) form fields
      • Initially only one field, click on add button new forms/fields created dynamically (+ Add Product, + Add Friend list, + Add Permanent & temporary address, etc.)
    • Custom Validation (Crossfield validations)- Password & Confirm Password validation, old & new password/pin validation etc.
    • Dynamic validation - If subscribed to notification than email field is mandatory, hierarchy/dependency based scenarios, If Married enter spouse details, etc.
  • Unit test - As logic is present in component class (Template Driven Forms we cant unit test HTML templates)

1.3. Steps to work with Reactive Model Driven Forms / Things to do with Reactive Model Driven Forms

  • Create & use new Angular CLI generated project
  • Add form HTML template/markup
  • Create a form model by using FormGroup and FormControl classes
  • Manage form control data/values
  • FormBuilder Service (a simpler way to specify/manage form model)
  • Validation implementation - Simple, Custom, Cross-field, and Dynamic validations
  • Add Dynamic form controls
  • Submit the form data to server

2 - Setting up new Angular project

  1. First check angular cli installed version details on machine by using command at command prompt: ng -v or ng --version



Angular CLI version
Image - Angular CLI version


  1. If angular CLI not installed/available on machine (no version or error message displayed) then install it by using the command: npm install -g @angular/cli@latest
  2. To update/upgrade angular CLI to the latest version, use following commands in sequence:
    • command: npm uninstall -g @angular/cli
    • command: npm cache verify or npm cache clean
    • command: npm install -g @angular/cli@latest
  3. Generate/create a new Angular app/project with Angular CLI - for dealing with angular forms with the syntax: ng new appName or ng new project-name, command: ng new angular-forms-reactivemodeldriven (after creation check the newly generated folder structure)



Angular project/app folder structure
Image - Angular project/app folder structure


  1. To run/serve application, at command prompt type command: ng serve or ng serve --port 5000 or ng serve --port 5000 -o (--port flag - to change the port number and -o or --open flag - to automatically launch/open app in browser)
  2. Go to the browser and load the application by typing address: http://localhost:4200/ or http://localhost:5000/
  3. Add the Bootstrap framework to an application (CSS framework used to make cool/intuitive User Interface and look/feel)
    • Download bootstrap css to local machine from bootstrap website: https://getbootstrap.com/docs/4.1/getting-started/download/ into folder assets/library/bootstrap.min.css
    • Include bootstrap in application - index.html under head tag - <link rel="stylesheet" href="./assets/library/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
    • or you can include a CDN path in index.html under head tag
    • or else you can install bootstrap with npm command: npm install bootstrap and use it



Bootstrap website - installation options
Image - Bootstrap website - installation options


  1. To verify bootstrap included/working properly in an application, check in Browser fonts, etc changed or not?
    • Also in app.component.html just create any simple component like buttons or divs with bootstrap class:
      • <button class="btn btn-success">Success Button</button> or
      • <div class="lead">Lead Heading</div>
      • Right click on element and check in inspect element the bootstrap class and properties applied to respective elements

Syntax & Example: index.html
```html
<!doctype html>



ReactiveModelDrivenForms









  1. 3 - Adding Form Markup-Template HTML
  2. =====================
  3. 3.1. Create an enrollment form with bootstrap classes:
  4. ---------------------
  5. - In file `app.component.html` create a registration form
  6. 1. Use bootstrap classes like `form-group` and `form-control` class with div and input field respectively to create form fields with standard look and feel
  7. 2. Create a user-name and email `input fields`
  8. 3. Create a password and confirm `password fields`
  9. 4. Create a submit button named `Register`
  10. > **Syntax & Example**: app.component.html
  11. ```html
  12. <div class="container-fluid mb-5">
  13. <h1>Registration Form</h1>
  14. <hr />
  15. <form>
  16. <!-- user name -->
  17. <div class="form-group">
  18. <label for="">Username:</label>
  19. <input type="text" class="form-control">
  20. </div>
  21. <!-- password -->
  22. <div class="form-group">
  23. <label for="">Password:</label>
  24. <input type="password" class="form-control">
  25. </div>
  26. <!-- confirm password -->
  27. <div class="form-group">
  28. <label for="">Confirm Password:</label>
  29. <input type="password" class="form-control">
  30. </div>
  31. <!-- register button -->
  32. <button class="btn btn-primary" type="submit">Register</button>
  33. </form>
  34. </div>



Bootstrap Registration Form
Image - Bootstrap Registration Form


4 - Creating the Form Model

To work with reactive/dynamic forms we need to import 'ReactiveFormsModule' which provides bunch of classes/directives/utilities (FormGroup & FormControl) necessary to build reactive/dynamic

4.1. 3 steps involved in creating reactive form:

  1. Define HTML <form> in component template/view/html file
  2. Define component model in component class/.ts file registrationForm = new FormGroup()
  3. Use directives provided by reactive forms module to associate the model with view <form [formGroup]="registrationForm"> and <input formControlName="userName">, <input formControlName="password">

Lets follow belows steps to achieve reactive forms 3 main steps:

  1. We have already added html form app.component.html in last step
  2. Now in app.module.ts:
    • import { ReactiveFormsModule } from ‘@angular/forms’;
    • also add to imports: [ ReactiveFormsModule ]
  3. FormGroup and FormControl are two important building blocks classes for reactive/dynamic forms
    • In Reactive forms, the form is represented by model in component class, FormGroup and FormControl classes used to make that model
    • FormGroup represents whole/entire form ( form is instance of FormGroup class )
    • FormControl represents each form field ( form fields are instance of FormControl class )
    • FormBuilder handle form control creation, dynamic/run time field/FormControl creation
    • Validators helps to setup validation on each form control

Syntax & Example: app.module.ts
```typescript
import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule } from ‘@angular/core’;

import { AppComponent } from ‘./app.component’;
import { ReactiveFormsModule } from ‘@angular/forms’;

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

  1. > **Syntax & Example**: app.component.ts
  2. ```typescript
  3. import { Component } from '@angular/core';
  4. import { FormGroup, FormControl } from '@angular/forms';
  5. @Component({
  6. selector: 'app-root',
  7. templateUrl: './app.component.html',
  8. styleUrls: ['./app.component.css']
  9. })
  10. export class AppComponent {
  11. // create a formgroup instance
  12. registrationForm = new FormGroup({
  13. // details of objects/controls present in html
  14. userName: new FormControl('Dinanath'), // defult value enter in bracket with quotes
  15. password: new FormControl(''),
  16. confirmPassword: new FormControl(''),
  17. });
  18. }

Syntax & Example: app.component.html
```html


Registration Form



{{ registrationForm.value | json }}

























  1. <p>
  2. <figure>
  3. <img src="./_images-angular-forms-reactivemodeldriven/04-01-01-form-value-object.png" alt="Form Model, FormControl/FormGroup - defualt values" title="Form Model, FormControl/FormGroup - defualt values" width="1000" border="2" />
  4. <figcaption> Image - Form Model, FormControl/FormGroup - defualt values</figcaption>
  5. </figure>
  6. </p>
  7. <hr/>
  8. <p>
  9. <figure>
  10. <img src="./_images-angular-forms-reactivemodeldriven/04-01-02-form-value-object.png" alt="Form Model, FormControl/FormGroup - updated values" title="Form Model, FormControl/FormGroup - updated values" width="1000" border="2" />
  11. <figcaption> Image - Form Model, FormControl/FormGroup - updated values</figcaption>
  12. </figure>
  13. </p>
  14. 5 - Nesting/Nested Form Groups
  15. =====================
  16. - Inside the main form i.e. FormGroup, We can create a other FormGroup and store smaller object properties/FormControl
  17. - create `'address'` a new FormGroup with 'street, landmark, road, postal-code' as a child FormControl
  18. - Under `'user details'` FormGroup create a username, gender, age, etc. as a child FormControl
  19. - In larger/complex forms such approach of creating Nested Form Groups will help to easily manage smaller chunks/sections
  20. > **Syntax & Example**: app.component.ts
  21. ```typescript
  22. // create a formgroup instance
  23. registrationForm = new FormGroup({
  24. // details of objects/controls present in html
  25. userName: new FormControl('Dinanath'), // defult value enter in bracket with quotes
  26. password: new FormControl(''),
  27. confirmPassword: new FormControl(''),
  28. // sub/nested formgroup
  29. address: new FormGroup({
  30. city: new FormControl('Mumbai'),
  31. state: new FormControl('Maharashtra'),
  32. postalcode: new FormControl(400001)
  33. })
  34. });

Syntax & Example: app.component.html
```html


Registration Form



Forms/FormGroup Values : registrationForm.value : {{ registrationForm.value | json }}

























formGroupName=”address: Nested FormGroup with child properties




























  1. <p>
  2. <figure>
  3. <img src="./_images-angular-forms-reactivemodeldriven/05-01-01-form-nested-form-group.png" alt="Reactive Form - nested FormGroup" title="Reactive Form - nested FormGroup" width="1000" border="2" />
  4. <figcaption> Image - Reactive Form - nested FormGroup</figcaption>
  5. </figure>
  6. </p>
  7. 6 - Managing Control Values with SetValue() & PatchValue()
  8. =====================
  9. - Lets learn how to set FormControl values without any user interaction (set values programatically)
  10. - We can retrieve back-end data by an API/service and set/update the values of FormControl with `'setValue()'` and `'PatchValue()'` method
  11. - **`setValue()`** method works on FormGroup as well as FormControl class, But it accepts exact object structure which matches FormGroup with exact keys as FormControl, no custom deletion or addition of keys/properties allowed (will get an error). setValue is very strict with maintaining the structure of FormGroup - we must provide all FormControl values - we have to fill up/set the value of all the fields
  12. - **`patchValue`** method works on FormGroup as well as FormControl class, it accepts any fields - we can provide/pass value of any required field/few of the fields - we can fill up/set the value of only required fields
  13. - app.component.html: Create a button and kn app.component.ts: define methods:
  14. - `<button class="btn btn-success ml-4" (click)="loadApiDataSetValue()">Load API Data <br/> SetValue() </button>`
  15. - `<button class="btn btn-secondary ml-4" (click)="loadApiDataPatchValue()">Load API Data <br> PatchValue() </button>`
  16. > **Syntax & Example**: app.component.ts
  17. ```typescript
  18. import { Component } from '@angular/core';
  19. import { FormGroup, FormControl } from '@angular/forms';
  20. @Component({
  21. selector: 'app-root',
  22. templateUrl: './app.component.html',
  23. styleUrls: ['./app.component.css']
  24. })
  25. export class AppComponent {
  26. // create a formgroup instance
  27. registrationForm = new FormGroup({
  28. // details of objects/controls present in html
  29. userName: new FormControl('Dinanath'), // defult value enter in bracket with quotes
  30. password: new FormControl(''),
  31. confirmPassword: new FormControl(''),
  32. // sub/nested formgroup
  33. address: new FormGroup({
  34. city: new FormControl('Mumbai'),
  35. state: new FormControl('Maharashtra'),
  36. postalcode: new FormControl(400001)
  37. })
  38. });
  39. loadApiDataSetValue() {
  40. console.log('loadApiDataSetValue ');
  41. The// setValue method works on FormGroup as well as FormControl class, But it accepts exact object structure which matches FormGroup with exact keys as FormControl, no custom deletion or addition of keys/properties allowed (will get an error). setValue is very strict with maintaining the structure of FormGroup - we must provide all FormControl values - we have to fill up/set the value of all the fields
  42. this.registrationForm.setValue({
  43. userName: 'Angular',
  44. password: 'Angular6', //error - password field required to match FormGroup
  45. confirmPassword: 'Angular6',
  46. address: {
  47. city: 'Google',
  48. state: 'Google Corp',
  49. postalcode: 12345,
  50. }
  51. })
  52. }
  53. loadApiDataPatchValue() {
  54. console.log('loadApiDataPatchValue ');
  55. // patch value method works on FormGroup as well as FormControl class, it accepts any fields - we can provide/pass the value of any required field/few of the fields - we can fill up/set the value of only required fields
  56. this.registrationForm.patchValue({
  57. // userName: 'React',
  58. // password: 'React2',
  59. // confirmPassword: 'React2',
  60. address: {
  61. city: 'Facebook',
  62. state: 'Facebook Corp',
  63. postalcode: 678901,
  64. }
  65. })
  66. }
  67. }

Syntax & Example: app.component.html
```html


Registration Form



  1. <span class="lead"><strong>Forms/FormGroup Values : registrationForm.value :</strong></span> {{ registrationForm.value | json }}
  2. <hr />
  3. <!-- associate the model with view -->
  4. <form [formGroup]="registrationForm">
  5. <!-- user name -->
  6. <div class="form-group">
  7. <label for="">Username:</label>
  8. <input formControlName="userName" type="text" class="form-control">
  9. </div>
  10. <!-- password -->
  11. <div class="form-group">
  12. <label for="">Password:</label>
  13. <input formControlName="password" type="password" class="form-control">
  14. </div>
  15. <!-- confirm password -->
  16. <div class="form-group">
  17. <label for="">Confirm Password:</label>
  18. <input formControlName="confirmPassword" type="password" class="form-control">
  19. </div>
  20. <hr />
  21. <h3>formGroupName="address: Nested FormGroup with child properties </h3>
  22. <div formGroupName="address">
  23. <div class="form-group">
  24. <label for="">City:</label>
  25. <input formControlName="city" type="text" class="form-control">
  26. </div>
  27. <div class="form-group">
  28. <label for="">State:</label>
  29. <input formControlName="state" type="text" class="form-control">
  30. </div>
  31. <div class="form-group">
  32. <label for="">Postal Code:</label>
  33. <input formControlName="postalcode" type="text" class="form-control">
  34. </div>
  35. </div>
  36. <hr />
  37. <!-- register button -->
  38. <button class="btn btn-primary" type="submit">Register</button>
  39. <button class="btn btn-success ml-4" (click)="loadApiDataSetValue()">Load API Data <br/> SetValue()
  40. </button>
  41. <button class="btn btn-secondary ml-4" (click)="loadApiDataPatchValue()">Load API Data <br/> PatchValue()
  42. </button>

  1. <p>
  2. <figure>
  3. <img src="./_images-angular-forms-reactivemodeldriven/06-01-01-form-setvalue.png" alt="Reactive Form - setValue() method" title="Reactive Form - setValue() method" width="1000" border="2" />
  4. <figcaption> Image - Reactive Form - setValue() method</figcaption>
  5. </figure>
  6. </p>
  7. <hr />
  8. <p>
  9. <figure>
  10. <img src="./_images-angular-forms-reactivemodeldriven/06-01-02-form-setvalue-error.png" alt="Reactive Form - setValue() method - error" title="Reactive Form - setValue() method - error" width="1000" border="2" />
  11. <figcaption> Image - Reactive Form - setValue() method - error</figcaption>
  12. </figure>
  13. </p>
  14. <hr />
  15. <p>
  16. <figure>
  17. <img src="./_images-angular-forms-reactivemodeldriven/06-01-03-form-patchvalue.png" alt="Reactive Form - patchValue() method" title="Reactive Form - patchValue() method" width="1000" border="2" />
  18. <figcaption> Image - Reactive Form - patchValue() method</figcaption>
  19. </figure>
  20. </p>
  21. 7 - FormBuilder Service
  22. =====================
  23. - Creating multiple instances of FormControl classes (name/password/email/address fields) manually is pretty time consuming and repetitive
  24. - `FormBuilder` service provides/consists of methods to handle/generate FormControls dynamically with lesser code
  25. - `FormBuilder` is an alternate simpler service to create FormGroup and FormControls
  26. - Instead of FormGroup and FormControl import FormBuilder service and inject in the constructor
  27. - Comment all old properties related to FormGroup and FormControl and create new with FormBuilder instance
  28. > **Syntax & Example**: app.component.ts
  29. ```typescript
  30. // import { FormGroup, FormControl } from '@angular/forms';
  31. import { FormBuilder } from '@angular/forms';
  32. constructor(private fb: FormBuilder) { }
  33. /* // create a formgroup instance
  34. registrationForm = new FormGroup({
  35. // details of objects/controls present in html
  36. userName: new FormControl('Dinanath'), // defult value enter in bracket with quotes
  37. password: new FormControl(''),
  38. confirmPassword: new FormControl(''),
  39. // sub/nested formgroup
  40. address: new FormGroup({
  41. city: new FormControl('Mumbai'),
  42. state: new FormControl('Maharashtra'),
  43. postalcode: new FormControl(400001)
  44. })
  45. }); */
  46. // create a FormBuilder instance
  47. registrationForm = this.fb.group({
  48. userName: ['Dinanath Test Name with Form Builder'],
  49. password: [''],
  50. confirmPassword: [''],
  51. address: this.fb.group({
  52. city: ['Mumbai'],
  53. state: ['Maharashtra'],
  54. postalcode: [400001]
  55. })
  56. })



Reactive Form - FormBuilder
Image - Reactive Form - FormBuilder


8 - Implementing simple Validation

For common use cases (validation purpose) Reactive forms uses set of validators functions in class file only, not in html/view/template

8.1 Steps to apply reactive forms validations:

  1. Apply validation rules to form control - use validators class
    • import Validators Class - import { FormBuilder, Validators } from '@angular/forms';
    • add validators to form control item - userName: ['Dinanath', Validators.required],
  2. Provide a visual feedback for validations
    • use class binding to show red border when form control/field is invalid - <input [class.is-invalid]="registrationForm.get('userName').invalid && registrationForm.get('userName').touched" formControlName="userName" type="text" class="form-control">
  3. Display a appropriate error message for validation
    • <small class="text-danger" [class.d-none]="registrationForm.get('userName').valid || registrationForm.get('userName').untouched">* Name is required</small>

Syntax & Example: app.component.ts
```typescript
import { FormBuilder, Validators } from ‘@angular/forms’;

// create a FormBuilder instance
registrationForm = this.fb.group({
userName: [‘Dinanath’, Validators.required],
password: [‘’],
confirmPassword: [‘’],
address: this.fb.group({
city: [‘Mumbai’],
state: [‘Maharashtra’],
postalcode: [400001]
})
})

  1. > **Syntax & Example**: app.component.html
  2. ```html
  3. <!-- user name -->
  4. <div class="form-group">
  5. <label for="">Username:</label>
  6. <!-- class binding to show red border when form control/field is invalid -->
  7. <input [class.is-invalid]="registrationForm.get('userName').invalid && registrationForm.get('userName').touched" formControlName="userName" type="text" class="form-control">
  8. <!-- single error with a class binding -->
  9. <small class="text-danger" [class.d-none]="registrationForm.get('userName').valid || registrationForm.get('userName').untouched">* Name is required</small>
  10. </div>

8.2 Multiple validation rules:

  • To apply more than one validation rules, convert Validators to an array - userName: ['Dinanath', [Validators.required, Validators.minLength(3)]],
  • With *ngIf directive show conditioinal error messages

Best practice to access formControl with Getter (keep the code short & simple)

  • Create getter method to return individual form control and use it in HTML file

Syntax & Example: app.component.ts
```typescript
// getter for userName control/field to keep code short in html file
get userNameControl(){
return this.registrationForm.get(‘userName’);
}

// create a FormBuilder instance
registrationForm = this.fb.group({
userName: [‘Dinanath’, [Validators.required, Validators.minLength(3)]],
password: [‘’],
confirmPassword: [‘’],
address: this.fb.group({
city: [‘Mumbai’],
state: [‘Maharashtra’],
postalcode: [400001]
})
})

  1. > **Syntax & Example**: app.component.html
  2. ```html
  3. <!-- user name -->
  4. <div class="form-group">
  5. <label for="">Username:</label>
  6. <!-- class binding to show red border when form control/field is invalid -->
  7. <!-- use getter method for userName control/field to keep code short in html file -->
  8. <input [class.is-invalid]="userNameControl.invalid &&
  9. userNameControl.touched" formControlName="userName" type="text" class="form-control">
  10. <!-- single error with a class binding -->
  11. <!-- <small class="text-danger" [class.d-none]="userNameControl.valid || userNameControl.untouched">* Name is required</small> -->
  12. <!-- group or multiple error messages : error property -->
  13. <div *ngIf="userNameControl.invalid && userNameControl.touched">
  14. <small class="text-danger" *ngIf="userNameControl.errors?.required">* Name is required</small>
  15. <small class="text-danger" *ngIf="userNameControl.errors?.minlength">* Name must be 3 characters</small>
  16. </div>
  17. </div>



Reactive Form - Validators.required
Image - Reactive Form - Validators.required





Reactive Form - Validators.required - show error text
Image - Reactive Form - Validators.required - show error text





Reactive Form - Validators.minlength
Image - Reactive Form - Validators.minlength





Reactive Form - Validators.minlength - show error text
Image - Reactive Form - Validators.minlength - show error text


9 - Custom Validation

  • Some times in-built validators not match with the exact scenario/requirements
  • We can create custom validators for such scenarios like some keywords (spam words) not allowed in user name field, etc.
  • Usually custom validators are used through-out the application so its advisable to create custom validators (function class) in an external class file and share
  • create folder: 'app/shared/validators' and inside this folder create a file named: 'user-name.validator.ts'
    • create a validator function
  • Lets use/import newly created custom validator 'user-name.validator.ts' in app.component.ts
    • import { userNameValidator } from './shared/validators/user-name.validators';

      userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
  • Add an error message realted to custom validator in view
    • <small class="text-danger" *ngIf="userNameControl.errors?.validateUserNameError">* '{{userNameControl.errors?.validateUserNameError.value}}' user name not allowed</small>

Syntax & Example: user-name.validators.ts
```typescript
import { AbstractControl } from “@angular/forms”;

// create a validator function to avoid junk/spam names like admin
// it returns string message or null
export function userNameValidator(control: AbstractControl): { [key: string]: any } | null {
const isUserNameCorrect = /junk/.test(control.value);
return isUserNameCorrect ? { ‘validateUserName’: { value: control.value } } : null;
}

  1. > **Syntax & Example**: app.component.ts
  2. ```typescript
  3. import { userNameValidator } from './shared/validators/user-name.validators';
  4. // create a FormBuilder instance
  5. registrationForm = this.fb.group({
  6. userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
  7. password: [''],
  8. confirmPassword: [''],
  9. address: this.fb.group({
  10. city: ['Mumbai'],
  11. state: ['Maharashtra'],
  12. postalcode: [400001]
  13. })
  14. })

Syntax & Example: app.component.html

  1. <!-- group or multiple error messages : error property -->
  2. <div *ngIf="userNameControl.invalid && userNameControl.touched">
  3. <small class="text-danger" *ngIf="userNameControl.errors?.required">* Name is required</small>
  4. <small class="text-danger" *ngIf="userNameControl.errors?.minlength">* Name must be 3 characters</small>
  5. <small class="text-danger" *ngIf="userNameControl.errors?.validateUserNameError">* '{{userNameControl.errors?.validateUserNameError.value}}' user name not allowed</small>
  6. </div>



Reactive Form - custom validators
Image - Reactive Form - custom validators





Reactive Form - custom validators - error text
Image - Reactive Form - custom validators - error text


10 - Cross Field Validation

  • Sometimes we need to validate values across multiple fields like ‘password & confirm password’, ‘card number’, ‘verification of pin number’ etc.
  • Lets create a custom validator to match ‘password & confirm password’ fields
  • In folder: 'app/shared/validators' create a file named: 'password.validator.ts'
  • Apply custom password validator to formgroup-the main form (not to password formControl)

Syntax & Example: password.validator.ts
```typescript
import { AbstractControl } from “@angular/forms”;

// create a validator function to match password and confirm password field
// it returns boolean or null
export function passwordValidator(control: AbstractControl): { [key: string]: boolean } | null {
const passwordControl = control.get(‘password’);
const confirmPasswordControl = control.get(‘confirmPassword’);

  1. if (passwordControl.pristine && confirmPasswordControl.pristine || passwordControl.untouched && confirmPasswordControl.untouched) {
  2. return null;
  3. }
  4. return passwordControl && confirmPasswordControl && passwordControl.value != confirmPasswordControl.value ? { 'misMatchError': true } : null;

}

  1. > **Syntax & Example**: app.component.ts
  2. ```typescript
  3. import { passwordValidator } from './shared/validators/password.validator';
  4. // create a FormBuilder instance
  5. registrationForm = this.fb.group({
  6. userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
  7. password: [''],
  8. confirmPassword: [''],
  9. address: this.fb.group({
  10. city: ['Mumbai'],
  11. state: ['Maharashtra'],
  12. postalcode: [400001]
  13. })
  14. }, {validator: passwordValidator})

Syntax & Example: app.component.html
```html


  1. <!-- class binding to show red border when form control/field is invalid -->
  2. <input [class.is-invalid]="registrationForm.errors?.passwordMisMatchError" formControlName="confirmPassword" type="password" class="form-control">
  3. <!-- single error with a class binding -->
  4. <small class="text-danger" *ngIf="registrationForm.errors?.passwordMisMatchError">Confirm Password not matched!</small>

  1. <p>
  2. <figure>
  3. <img src="./_images-angular-forms-reactivemodeldriven/10-01-01-custom-cross-field-validator-error.png" alt="Reactive Form - custom validators - cross field - error text" title="Reactive Form - custom validators - cross field - error text" width="1000" border="2" />
  4. <figcaption> Image - Reactive Form - custom validators - cross field - error text</figcaption>
  5. </figure>
  6. </p>
  7. 11 - Conditional Dynamic Validation
  8. =====================
  9. - Sometimes we need to apply dynamic validation like after certain action/thing/condition, validations should come in to the picture
  10. - Example: After subscribe checkbox selection -> email field mendatory,
  11. - Create `email text field` and `subscption checkbox field` in form
  12. - We need to track value of checkbox and conditionaly set status of email field
  13. - `valueChanges` property helps to track the current value of any controls as a observables
  14. - `setValidators` methods - set desired validators to formControl/field
  15. - `clearValidators` methods - clears validators from formControl/field
  16. - Finally we need to invoke/call `updateValueAndValidity` method to reflect latest status
  17. > **Syntax & Example**: app.component.html
  18. ```html
  19. <!-- email -->
  20. <div class="form-group">
  21. <label for="">Email:</label>
  22. <!-- class binding to show red border when form control/field is invalid -->
  23. <input [class.is-invalid]="emailControl.invalid &&
  24. emailControl.touched" formControlName="email" type="text" class="form-control">
  25. <!-- single error with a class binding -->
  26. <small class="text-danger" [class.d-none]="emailControl.valid || emailControl.untouched">* Email is required</small>
  27. </div>
  28. <!-- subscribe checkbox -->
  29. <div class="form-check mb-3">
  30. <input formControlName="subscribe" type="checkbox" class="form-check-input">
  31. <label for="" class="form-check-label">Subscribe/Send me promotion offers</label>
  32. </div>

Syntax & Example: app.component.ts
```typescript
import { Component, OnInit } from ‘@angular/core’;
// import { FormGroup, FormControl } from ‘@angular/forms’;
import { FormBuilder, Validators, FormGroup } from ‘@angular/forms’;
import { userNameValidator } from ‘./shared/validators/user-name.validators’;
import { passwordValidator } from ‘./shared/validators/password.validator’;

@Component({
selector: ‘app-root’,
templateUrl: ‘./app.component.html’,
styleUrls: [‘./app.component.css’]
})

export class AppComponent implements OnInit {

registrationForm: FormGroup;

// getter for userName field to keep code short in html file
get userNameControl(){
return this.registrationForm.get(‘userName’);
}

// getter for email control/field to keep code short in html file
get emailControl(){
return this.registrationForm.get(‘email’);
}

constructor(private fb: FormBuilder) { }

ngOnInit() {
// create a FormBuilder instance
this.registrationForm = this.fb.group({
userName: [‘Dinanath’, [Validators.required, Validators.minLength(3), userNameValidator]],
email:[‘’],
subscribe:[false],
password: [‘’],
confirmPassword: [‘’],
address: this.fb.group({
city: [‘Mumbai’],
state: [‘Maharashtra’],
postalcode: [400001]
})
}, {validator: passwordValidator});

  1. // subscribe checkbox
  2. this.registrationForm.get('subscribe').valueChanges
  3. .subscribe(subscribeCheckedValue => {
  4. const email = this.registrationForm.get('email');
  5. // email field set/unset `required` validators
  6. if(subscribeCheckedValue){
  7. email.setValidators(Validators.required);
  8. } else {
  9. email.clearValidators();
  10. }
  11. // to reflect latest correct status
  12. email.updateValueAndValidity();
  13. })

}

loadApiDataSetValue() {
console.log(‘loadApiDataSetValue ‘);

  1. this.registrationForm.setValue({
  2. userName: 'Angular',
  3. password: 'Angular6',
  4. confirmPassword: 'Angular6',
  5. address: {
  6. city: 'Google',
  7. state: 'Google Corp',
  8. postalcode: 12345,
  9. }
  10. })

}

loadApiDataPatchValue() {
console.log(‘loadApiDataPatchValue ‘);

  1. this.registrationForm.patchValue({
  2. // userName: 'React',
  3. // password: 'React2',
  4. // confirmPassword: 'React2',
  5. address: {
  6. city: 'Facebook',
  7. state: 'Facebook Corp',
  8. postalcode: 678901,
  9. }
  10. })

}

}

  1. <p>
  2. <figure>
  3. <img src="./_images-angular-forms-reactivemodeldriven/11-01-01-conditional-validator-error.png" alt="Reactive Form - custom validators - conditional/hierarchy field - error text" title="Reactive Form - custom validators - conditional/hierarchy field - error text" width="1000" border="2" />
  4. <figcaption> Image - Reactive Form - custom validators - conditional/hierarchy field - error text</figcaption>
  5. </figure>
  6. </p>
  7. <hr/>
  8. <p>
  9. <figure>
  10. <img src="./_images-angular-forms-reactivemodeldriven/11-01-02-conditional-validator-success.png" alt="Reactive Form - custom validators - conditional/hierarchy field - success" title="Reactive Form - custom validators - conditional/hierarchy field - success" width="1000" border="2" />
  11. <figcaption> Image - Reactive Form - custom validators - conditional/hierarchy field - success</figcaption>
  12. </figure>
  13. </p>
  14. 12 - Dynamic Form Controls
  15. ======================
  16. - In some scenarios, we need to add fields/records on the fly like click on `Add Patient` button to add new patients record and so on, provide alternate email, contact details, etc.
  17. - The method of adding new fields at run-time keeps form concise and expand only when necessary
  18. - `FormArray` class helps to maintain and duplicate dynamic list of controls
  19. - import { FormBuilder, Validators, FormGroup, FormArray } from '@angular/forms';
  20. - Define `FormArray` in 'formModel'
  21. - alternateEmailAddresses:this.fb.array([])
  22. - Create `getter method` to return individual form control and use/access easily it in HTML file
  23. - Create a method to dynamically insert controls to FormArray
  24. - In view create a button `Add alternate Email` to invoke a method to add/push dynamic controls
  25. > **Syntax & Example**: app.component.ts
  26. ```typescript
  27. import { Component, OnInit } from '@angular/core';
  28. // import { FormGroup, FormControl } from '@angular/forms';
  29. import { FormBuilder, Validators, FormGroup, FormArray } from '@angular/forms';
  30. import { userNameValidator } from './shared/validators/user-name.validators';
  31. import { passwordValidator } from './shared/validators/password.validator';
  32. @Component({
  33. selector: 'app-root',
  34. templateUrl: './app.component.html',
  35. styleUrls: ['./app.component.css']
  36. })
  37. export class AppComponent implements OnInit {
  38. registrationForm: FormGroup;
  39. // getter for userName field to keep code short in html file
  40. get userNameControl(){
  41. return this.registrationForm.get('userName');
  42. }
  43. // getter for email control/field to keep code short in html file
  44. get emailControl(){
  45. return this.registrationForm.get('email');
  46. }
  47. // getter for email control/field to keep code short in html file
  48. get alternateEmailAddressesControl(){
  49. return this.registrationForm.get('alternateEmailAddresses') as FormArray
  50. }
  51. addAlternateEmailAddresses() {
  52. return this.alternateEmailAddressesControl.push(this.fb.control(''));
  53. }
  54. constructor(private fb: FormBuilder) { }
  55. ngOnInit() {
  56. // create a FormBuilder instance
  57. this.registrationForm = this.fb.group({
  58. userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
  59. email:[''],
  60. subscribe:[false],
  61. password: [''],
  62. confirmPassword: [''],
  63. address: this.fb.group({
  64. city: ['Mumbai'],
  65. state: ['Maharashtra'],
  66. postalcode: [400001]
  67. }),
  68. alternateEmailAddresses:this.fb.array([])
  69. }, {validator: passwordValidator});
  70. // subscribe checkbox
  71. this.registrationForm.get('subscribe').valueChanges
  72. .subscribe(subscribeCheckedValue => {
  73. const email = this.registrationForm.get('email');
  74. // email field set/unset `required` validators
  75. if(subscribeCheckedValue){
  76. email.setValidators(Validators.required);
  77. } else {
  78. email.clearValidators();
  79. }
  80. // to reflect latest correct status
  81. email.updateValueAndValidity();
  82. })
  83. }
  84. loadApiDataSetValue() {
  85. console.log('loadApiDataSetValue ');
  86. this.registrationForm.setValue({
  87. userName: 'Angular',
  88. password: 'Angular6',
  89. confirmPassword: 'Angular6',
  90. address: {
  91. city: 'Google',
  92. state: 'Google Corp',
  93. postalcode: 12345,
  94. }
  95. })
  96. }
  97. loadApiDataPatchValue() {
  98. console.log('loadApiDataPatchValue ');
  99. this.registrationForm.patchValue({
  100. // userName: 'React',
  101. // password: 'React2',
  102. // confirmPassword: 'React2',
  103. address: {
  104. city: 'Facebook',
  105. state: 'Facebook Corp',
  106. postalcode: 678901,
  107. }
  108. })
  109. }
  110. }

Syntax & Example: app.component.html
```html


* Email is required



  1. <p>
  2. <figure>
  3. <img src="./_images-angular-forms-reactivemodeldriven/12-01-01-dynamic-form-control.png" alt="Reactive Form - add dynamic email field/control" title="Reactive Form - add dynamic email field/control" width="1000" border="2" />
  4. <figcaption> Image - Reactive Form - add dynamic email field/control</figcaption>
  5. </figure>
  6. </p>
  7. 13 - Submitting Form Data
  8. ======================
  9. 1. Use `'novalidate'` attribute to form tag to avoid/prevent browser `default validations` when will click on 'SUBMIT' button.
  10. 2. Bind `'ngSubmit'` event to the form tag which will trigger on 'SUBMIT' button
  11. - ```<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
  1. Define onSubmit() event handler in app.component.ts class file

Syntax & Example: app.component.html

  1. <form [formGroup]="registrationForm" novalidate (ngSubmit)="onSubmit()">

Syntax & Example: app.component.ts

  1. // handler for submit button
  2. onSubmit() {
  3. console.log('submit button clicked');
  4. console.log(this.registrationForm.value);
  5. }
  1. To send data to the server we need to create/use 'registration service' with angular CLI by using the command: ng generate service registration or ng g s registration
    • registration.service.ts:
      • Import HttpClient module: import { HttpClient } from ‘@angular/common/http’;
      • Invoke HttpClient in constructor as a local variable / Dependency injection:

        constructor(public _httpClient:HttpClient) { }
  2. app.module.ts:
    • import HttpClientModule: import { HttpClientModule } from ‘@angular/common/http’;
    • add to the imports array:

      imports: [
      BrowserModule,
      FormsModule,
      HttpClientModule
      ],
  3. registration.service.ts:
    • // create a variable which hold path to which will post the date

      _url = ‘http://localhost:3000/enroll‘;
    • // create a method called register which will post the data to server
      register(userData) {
      return this._httpClient.post<any>(this._url, userData);
      }
  4. app.component.ts:

    • The Post request will return response as an observable, so we need to subscribe to observables in app.component.ts
    • Import registration service: import { RegistrationService } from ‘./registration.service’;
    • Invoke in constructor as a local variable / Dependency injection:

      constructor(public registrationService:RegistrationService) { }
    • On submit button clicked i.e. in onSubmit() method subscribe to the observables:

      // handler for submit button
      onSubmit() {
      console.log(‘submit button clicked’);
      console.log(this.registrationForm.value);

      this.registrationService.register(this.registrationForm.value)
      .subscribe(
      response => console.log(‘Success’, response),
      error => console.log(‘Error’, error)
      )
      }

Syntax & Example: app.module.ts
```typescript
import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule } from ‘@angular/core’;
import { HttpClientModule } from ‘@angular/common/http’;

import { AppComponent } from ‘./app.component’;
import { ReactiveFormsModule } from ‘@angular/forms’;
import { FormBuilderComponent } from ‘./components/form-builder/form-builder.component’;

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

  1. > **Syntax & Example**: registration.service.ts
  2. ```typescript
  3. import { Injectable } from '@angular/core';
  4. import { HttpClient, HttpErrorResponse } from '@angular/common/http';
  5. // to catch error
  6. import { catchError } from 'rxjs/operators';
  7. import { throwError } from 'rxjs';
  8. @Injectable({
  9. providedIn: 'root'
  10. })
  11. export class RegistrationService {
  12. // create a variable which holds the path to which will post the date
  13. _url = 'http://localhost:3000/enroll';
  14. constructor(public _httpClient: HttpClient ) { }
  15. // create a method called enroll which will post the data to server
  16. register(userData) {
  17. return this._httpClient.post<any>(this._url, userData)
  18. .pipe(catchError(this.errorHandler)) //catch errors
  19. }
  20. errorHandler(error: HttpErrorResponse) {
  21. return throwError(error);
  22. }
  23. }

Syntax & Example: app.component.ts
```typescript
import { Component, OnInit } from ‘@angular/core’;
// import { FormGroup, FormControl } from ‘@angular/forms’;
import { FormBuilder, Validators, FormGroup, FormArray } from ‘@angular/forms’;
import { userNameValidator } from ‘./shared/validators/user-name.validators’;
import { passwordValidator } from ‘./shared/validators/password.validator’;
import { RegistrationService } from ‘./registration.service’;

@Component({
selector: ‘app-root’,
templateUrl: ‘./app.component.html’,
styleUrls: [‘./app.component.css’]
})

export class AppComponent implements OnInit {

registrationForm: FormGroup;

// getter for userName field to keep code short in html file
get userNameControl() {
return this.registrationForm.get(‘userName’);
}

// getter for email control/field to keep code short in html file
get emailControl() {
return this.registrationForm.get(‘email’);
}

// getter for email control/field to keep code short in html file
get alternateEmailAddressesControl() {
return this.registrationForm.get(‘alternateEmailAddresses’) as FormArray
}

addAlternateEmailAddresses() {
return this.alternateEmailAddressesControl.push(this.fb.control(‘’));
}

// create a new data member/property to bind to the view
errorMessage = ‘’;

// to check form submitted or not
isFormSubmitted = false;

constructor(private fb: FormBuilder, public registrationService: RegistrationService) { }

ngOnInit() {
// create a FormBuilder instance
this.registrationForm = this.fb.group({
userName: [‘Dinanath’, [Validators.required, Validators.minLength(3), userNameValidator]],
email: [‘’],
subscribe: [false],
password: [‘’],
confirmPassword: [‘’],
address: this.fb.group({
city: [‘Mumbai’],
state: [‘Maharashtra’],
postalcode: [400001]
}),
alternateEmailAddresses: this.fb.array([])
}, { validator: passwordValidator });

// subscribe checkbox
this.registrationForm.get(‘subscribe’).valueChanges
.subscribe(subscribeCheckedValue => {
const email = this.registrationForm.get(‘email’);

  1. // email field set/unset `required` validators
  2. if (subscribeCheckedValue) {
  3. email.setValidators(Validators.required);
  4. } else {
  5. email.clearValidators();
  6. }
  7. // to reflect latest correct status
  8. email.updateValueAndValidity();
  9. })

}

loadApiDataSetValue() {
console.log(‘loadApiDataSetValue ‘);

  1. this.registrationForm.setValue({
  2. userName: 'Angular',
  3. password: 'Angular6',
  4. confirmPassword: 'Angular6',
  5. address: {
  6. city: 'Google',
  7. state: 'Google Corp',
  8. postalcode: 12345,
  9. }
  10. })

}

loadApiDataPatchValue() {
console.log(‘loadApiDataPatchValue ‘);

  1. this.registrationForm.patchValue({
  2. // userName: 'React',
  3. // password: 'React2',
  4. // confirmPassword: 'React2',
  5. address: {
  6. city: 'Facebook',
  7. state: 'Facebook Corp',
  8. postalcode: 678901,
  9. }
  10. })

}

// handler for submit button
onSubmit() {
console.log(‘submit button clicked’);
console.log(this.registrationForm.value);
this. isFormSubmitted = true;
this.registrationService.register(this.registrationForm.value)
.subscribe(
response => console.log(‘Success’, response),
// error => console.log(‘Error’, error)

  1. // store error in data member / property to bind to the view
  2. error => this.errorMessage = error.statusText
  3. )

}
}

  1. <p>
  2. <figure>
  3. <img src="./_images-angular-forms-reactivemodeldriven/13-01-01-submit-form-data.png" alt="Reactive Form - Submit Form data" title="Reactive Form - Submit Form data" width="1000" border="2" />
  4. <figcaption> Image - Reactive Form - Submit Form data</figcaption>
  5. </figure>
  6. </p>
  7. 14 - Express Server to Receive Form Data
  8. =====================
  9. 1. At the root, besides the angular application folder create a new siblings folder named `'server'` which consists of server-side code
  10. 2. Run the command: `npm init --yes` to create a `package.json` also bypass-surpass the questions with default answers (without giving answers to questions)
  11. 3. Install express and other dependencies with the command: <br/>
  12. `npm install --save express body-parser cors` <br/>

“dependencies”: {
“body-parser”: “^1.18.3”, - middleware to handle form data
“cors”: “^2.8.4”, - helps to make request through multiple ports/servers - cross origin resource sharing
“express”: “^4.16.3” - web server
}

  1. 4. Inside a `server` folder create a new file named `'server.js'`
  2. 5. at command prompt/terminal run command: `node server` - will get output in terminal as 'Server running on localhost port: 3000'
  3. 6. in browser type path: `'http://localhost:3000/'` - output - 'Hello from the server!'
  4. 7. add an endpoint in server.js to which angular application will post data
  5. // add endpoint
  6. app.post('/enroll', function(req,res){
  7. // req.body - contains user data send by the angular
  8. console.log(req.body);
  9. // send response
  10. res.status(200).send({'message': 'Data received'});
  11. })
  12. 8. insert/add endpoint path to angular url variable in 'enrollment.service.ts'
  13. _url = 'http://localhost:3000/enroll';
  14. 9. restart the node server by command: `node server`
  15. 10. In angular application click on the submit button and check `inspect element` console as well as node console will get the message and user data as an output. for better usability its advisable to hide actual form/hide submit button / disable submit button etc. to avoid the extra click on submit button.
  16. > **Syntax & Example**: server.ts
  17. ```typescript
  18. // 1. imports/requires the packages
  19. const express = require('express');
  20. const bodyParser = require('body-parser');
  21. const cors = require('cors');
  22. // port
  23. const PORT = 3000;
  24. const app = express();
  25. // handle the json data
  26. app.use(bodyParser.json());
  27. app.use(cors());
  28. // test/check get request
  29. app.get('/',function(req, res){
  30. res.send('Hello from server!');
  31. })
  32. // add endpoint
  33. app.post('/enroll', function(req,res){
  34. // req.body - contains user data send by the angular
  35. console.log(req.body);
  36. // send response
  37. res.status(200).send({'message': 'Data received'});
  38. // to see errors
  39. // res.status(401).send({'message': 'Data received'});
  40. })
  41. // listen to request
  42. app.listen(PORT, function(){
  43. console.log('Server running on localhost port: ', PORT);
  44. })



Reactive Form - Submit Form data with Node Server
Image - Reactive Form - Submit Form data with Node Server





Reactive Form - Submit Form data with Node Server Response
Image - Reactive Form - Submit Form data with Node Server Response