© Venkata Keerti Kotaru 2020
V. K. KotaruAngular for Material Designhttps://doi.org/10.1007/978-1-4842-5434-9_9

9. Angular: Building Forms

Venkata Keerti Kotaru1 
(1)
Hyderabad, India
 
Traditionally, forms are an important aspect of a web application. Data is input through a form, which is validated and saved in the back end. Angular has a sophisticated approach to handling forms and user data. The framework provides two choices for creating forms.
  • Template-driven forms: Simple and easy to build

  • Reactive forms: Model driven and useful for creating complex forms.

Template-Driven Forms

Template-driven forms address many typical use cases for handling user data. They are easy to build, asynchronous, and the data is mutable (changes to the form to update the model object or the TypeScript class field).

Note

Mutable JavaScript objects allow you to update the value after it was created. It is good practice to use immutable objects that do not allow an object value to change. For reliability, especially when comparing objects, using immutable JavaScript objects is advised. They create a new object for each change.

Getting Started with Template-Driven Forms

In the Superhero application, SuperheroesMaterialDesignModule’s components have Angular Material dependencies. To implement template-driven forms, we use directives and providers that are defined in an Angular module called FormsModule.

To get started, import FormsModule to SuperheroesMaterialDesignModule. In Listing 9-1, note lines 1 and 5.
1. import { FormsModule } from '@angular/forms';
2. @NgModule({
3. // Removed code for brevity
4. imports: [
5.     FormsModule
6. ],
7. exports: [
8.    // Removed code for brevity
9. ],
10. providers:[]
11. })
12. export class SuperheroesMaterialDesignModule { }
Listing 9-1

Import Forms Module

Approach to Building Forms

In the sample application, we built a component (named app-create-superhero) to create a superhero. Consider creating a model object that represents the create superhero form. This is a TypeScript object representing a superhero form. We use two-way data binding and the ngModel directive to bind each control in the template file with the TypeScript model object. Figure 9-1 visualizes the model object representing the form.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig1_HTML.jpg
Figure 9-1

Model representing the form

To create a TypeScript class (that will be used as a model) with Angular CLI, use the command in Listing 9-2.
ng g class superheroes-material-design/models/superhero
Listing 9-2

Create TypeScript Class Used as a Model

In the superhero class, add fields that represent controls on the form (see Figure 9-1). The multiselect controls drop-down (for special powers) and chips (for favorite food) use an array in the model. In this form, the remaining fields are strings.

Import the class to the CreateSuperheroComponent (TypeScript class for app-create-superhero) component. Create a class field and instantiate it. In Listing 9-3, note lines 1 and 11.
1. import { Superhero } from '../models/superhero';
// Removed code for brevity
2. @Component({
3. selector: 'app-create-superhero',
4. templateUrl: './create-superhero.component.html',
5. styleUrls: ['./create-superhero.component.css']
6. })
7. export class CreateSuperheroComponent implements OnInit {
8. // Removed code for brevity
9. superhero: Superhero;
10. constructor() {
11.   this.superhero = new Superhero();
12. }
13.  ngOnInit() {
14. }
15.}
Listing 9-3

Using the Model in the Component

Next, enhance the template. Wrap the text fields and drop-downs in the superhero form with a form element. Angular automatically adds the ngForm directive to every form element. The directive is part of FormsModule, which we imported earlier.

Listing 9-4 adds a template reference variable, #superheroForm, which we will use later to refer to the form.
<form #superheroForm="ngForm">
   <div>
        <!-- Mat Form Field for name goes here
             Removed code for brevity -->
   </div>
   <div>
        <!-- Mat Form Field for details goes here
             Removed code for brevity -->
   </div>
   <div>
        <!-- Mat Form Field for country goes here
             Removed code for brevity -->
   </div>
   <div>
        <!-- Mat Form Field for special powers goes here
             Removed code for brevity -->
   </div>
   <div>
        <!-- Mat Form Field for favorite food goes here
             Removed code for brevity -->
   </div>
</form>
Listing 9-4

Wrap Form Controls in a Form

Next, use two-way data binding with model object to the text fields and drop-downs in the template.

The ngModel is a directive available in FormsModule (see Listing 9-4). The module has already been imported. It is ready to use in SuperheroesMaterialDesignModule. Remember, the app-create-superhero component is part of the same module.

Listing 9-5 (line 5) shows two-way data binding with ngModel in the text field. We use a name field as an example. The name field is in the superhero object in the TypeScript class component.
1. <div>
2.    <!-- Appearance: Outline -->
3.    <mat-form-field appearance="outline">
4.    <mat-label>Superhero Name</mat-label>
5.    <input type="text" name="name" [(ngModel)]="superhero.name" matInput required placeholder="A name to be remembered" />
6.     <mat-hint>Enter name</mat-hint>
7.       </mat-form-field>
8. </div>
Listing 9-5

ngModel on Text Field

Note

The text field also uses the matInput directive, which gives it a Material Design look and feel. In Listing 9-5, the input type is “text”. As long as the input type is supported by matInput, ngModel can be used in conjunction (see Chapter 6’s “Input Types with Matinput” section for a list of input types supported by matInput.

When building forms with template-driven forms, we use two-way data binding on all the form controls. In a drop-down’s mat-select elements, we can use the value field for two-way data binding. Listing 9-6’s line 3 is an attribute provided by the mat-select component.
1.       <mat-form-field appearance="outline">
2.         <mat-label>Country</mat-label>
3.         <mat-select placeholder="select country" [(value)]="superhero.country">
4.           <mat-option>None</mat-option>
5.           <mat-optgroup label="Earth">
6.             <mat-option value="in">India</mat-option>
7.             <mat-option value="us">United States</mat-option>
8.           </mat-optgroup>
9.           <mat-optgroup label="Outer Space">
10.             <mat-option value="os">Asgard</mat-option>
11.           </mat-optgroup>
12.         </mat-select>
13.       </mat-form-field>
Listing 9-6

Two-Way Data Binding with Value Field on mat-select Component

Note

For consistency, you may use [(ngModel)] with mat-select. If FormsModule has already been imported, use ngModel. The value is useful when ngModel is not available.

To demonstrate two-way data binding, let’s print the model object in the template. For debugging purposes, it is temporary; we will delete it eventually.

To print a stringified version of the model object, write a getter function in the component class (see Listing 9-7).
 get model(){
   return JSON.stringify(this.superhero);
 }
Listing 9-7

Getter Function Printing Model Object

Use this function in the component template (see Listing 9-8).
     <strong>
       {{model}}
     </strong>
Listing 9-8

Show Stringified Version of the Model Object

Note

Alternatively, we may use {superhero | json} in the HTML template. It will print the superhero JSON object.

Figure 9-2 shows a user entering values into the form fields.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig2_HTML.jpg
Figure 9-2

Two-way data binding with ngModel

Note the stringified model object at the bottom of the screen. The text is updated as the user enters data in real time. Let’s say that we reset the model object when the cancel button is clicked, and all the text fields are cleared. No additional action needs to be performed on the DOM.

Form Validation and Errors

Consider the following validators in template-driven forms. They are available out of the box to use along with form fields. They ensure the validity and correctness of the form and the data keyed in by the user.

Required

In the HTML template, the mandatory fields are marked with the required attribute (see Listing 9-8). The name and specialPowers fields are marked as required (see lines 6 and 28). The form is invalid without a value in these fields.

Email

Add the email attribute to validate that the provided text is an email. Let’s add email fields to the template and modify the Superhero model object to reflect the same (see Listing 9-8 line 13). The form is invalid without a valid email address in the [email protected] format.

Minlength

Enforce a minimum length for the text in a form field by using the minlength attribute. In the example, the details field enforces a minimum length of four characters. The form is invalid without a minimum of four characters in it (see Listing 9-8 line 20).

Maxlength

Enforce a maximum length for the text in a form field by using the maxlength attribute. In the example, consider the details field, which has a maximum length of 100 characters. The form is invalid if the text length exceeds this. A control with maxlength does not allow users to enter more than 100 characters (see Listing 9-9 line 20).
1. <form #superheroForm="ngForm">
2.   <div>
3.       <!-- Appearance: Outline -->
4.       <mat-form-field appearance="outline">
5.         <mat-label>Superhero Name</mat-label>
6.         <input type="text" name="name" [(ngModel)]="superhero.name" matInput required placeholder="A name to be remembered" />
7.         <mat-hint>Enter name</mat-hint>
8.       </mat-form-field>
9.     </div>
10.     <div>
11.         <mat-form-field appearance="outline">
12.           <mat-label>Email</mat-label>
13.          <input type="text" name="email" email [(ngModel)]="superhero.email" matInput  />
14.          <mat-hint>How do I contact the superhero for help?</mat-hint>
15.        </mat-form-field>
16.      </div>
17.    <div>
18.      <mat-form-field appearance="outline">
19.        <mat-label>Powers</mat-label>
20.        <textarea name="details" [(ngModel)]="superhero.details" minlength="4" maxlength="10" cdkTextareaAutosize cdkAutosizeMinRows="4" cdkAutosizeMaxRows="10" rows="6" matInput></textarea>
21.        <mat-hint>Explain superhero powers</mat-hint>
22.      </mat-form-field>
23.    </div>
24.    <div>
25.      <!-- Appearance: Outline -->
 26      <mat-form-field appearance="outline">
27.        <mat-label>Special Powers</mat-label>
28.        <mat-select required name="powers" [(ngModel)]="superhero.specialPowers" multiple>
29.          <mat-option>None</mat-option>
30.          <mat-option value="fly">fly</mat-option>
31.          <mat-option value="hammer">wield hammer</mat-option>
32.          <mat-option value="power">grow powerful</mat-option>
33.        </mat-select>
34.      </mat-form-field>
35.      <!-- <strong> My superhero can " {{powers}} "</strong> -->
36    </div>
37. </form>
Listing 9-9

Template-driven Forms, Validators

Using the State Field in Form Controls

When we use ngModel on a form, it tracks the state of the form fields. Form fields could be drop-downs, text fields, and so forth. The following states are possible.
  • Touched/untouched : If the user touched, visited, or focused the keyboard in the form field, it is touched. By default, all form fields are untouched, and Angular applies the ng-untouched CSS class on them. When the user focuses on the form field, it is replaced with an ng-touched CSS class.

  • Dirty/pristine : If the user attempts to change a value in a form field, it is in a dirty state. By default, all form fields are pristine. Angular applies the ng-prisitine CSS class. When the user brings the focus to the form field, it’s replaced with an ng-touched CSS class.

  • Valid/invalid : As you saw in the previous section, validations can be applied on form fields. When there is no validation error identified, a form field is valid. Angular applies the ng-valid CSS class; otherwise, the form could be invalid. For example, when a required field is touched but does not have a value, an ng-invalid CSS class is applied.

Angular Material’s mat-form-field component adds behavior when working with forms. It adds a highlighted color border to an invalid form field, which indicates that something is wrong with the field. Figure 9-3 is an email field with an invalid email value.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig3_HTML.jpg
Figure 9-3

mat-form-field indicating invalid status

In the default theme, floating labels, the cursor, and the border are marked red.

In Figure 9-3, the highlighted border indicates that something is wrong with the form field, but it does not present a friendly message to the user. Listing 9-10 line 3 uses the #email template reference variable assigned to ngModel. Now, the reference variable represents the form field status.
1.         <mat-form-field appearance="outline">
2.         <mat-label>Email</mat-label>
3.         <input #email="ngModel" type="text" name="email" email [(ngModel)]="superhero.email" matInput  />
4.         <mat-hint>How do I contact the superhero for help?</mat-hint>
5.         </mat-form-field>
Listing 9-10

Input Element with Template Reference Variable Assigned with ngModel

The following describe a status in the form field.
  • valid: This is true when the form field is valid. The usage is email.valid results in true/false.

  • pristine: This is true when the user did not attempt to change the form field’s value. The usage is email pristine. It is true when it’s not dirty.

  • touched: This is true when the user did not focus the keyboard in the form field. The usage is email touched. It is true when the user focuses on the field.

Listing 9-11 shows a message if the user touches the field and the state is invalid (notice line 2, in which the negation is on email.valid). We check the touched status since most form fields load with an invalid status when the form loads first. It needs to be valid by the time the user is ready to submit the form.
1.  <!-- Show error message to the user -->
2.  <div class="error-message" *ngIf="!email.valid && email.touched">
3.    Please enter a valid email address.
4.  </div>
Listing 9-11

Show a Friendly Error Message to the User

The result is shown in Figure 9-4.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig4_HTML.jpg
Figure 9-4

Email field with friendly error message

Note

In Figure 9-4, the initial fields on the class did not include email. Add an email field of type string in the TypeScript class component.

State of the Form

Note the first line in Listing 9-11. We created a template reference variable for the entire form element. All the state flags described in the previous section are also available in the form reference. The previous section described state fields in form fields, not the form itself. Listing 9-12 prints a form’s state fields.
<form #superheroForm="ngForm">
 <strong>
   Form Status, Valid- {{superheroForm.valid}}. Pristine- {{superheroForm.pristine}}. Touched- {{superheroForm.touched}}
 </strong>
<!-- removed code for brevity -->
Listing 9-12

Print Form Status

The result is shown in Figure 9-5. The form loads with an invalid status because most form fields do not have data. It is pristine because no form field is edited. The touched status is false because the keyboard focus was not brought onto any form field.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig5_HTML.jpg
Figure 9-5

Default status of a form

In Figure 9-5 and Listing 9-12, we print the state for demonstration purposes. How do we use these fields in a real-world scenario? In Listing 9-13, we enable the form’s submit buttons if the form is valid (notice the negation). This is part of the validation to ensure that good data makes it into the database from the form.
     <div>
       <button mat-stroked-button> Cancel </button>
       <button mat-stroked-button [disabled]="!superheroForm.valid"> Save </button>
     </div>
Listing 9-13

Submit Disabled Unless the Form Is Valid

Figure 9-6 shows the result.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig6_HTML.jpg
Figure 9-6

Disable submit/save on an invalid form

Note

Listing 9-12 and Listing 9-13 used various Angular Material components and directives, mat-select, mat-button/mat-stroked-button, MatInput, and so forth. So far, we have imported Angular modules, such as MatSelectModule, MatButtonModule, MatInputModule, and so forth. Import respective modules to SuperheroesMaterialDesignModule, if you have not done so already.

Reactive Forms

Reactive forms are model driven, immutable, and synchronous.
  • Model driven: A data object maintained in the component representing the form.

  • Immutable: Every change to the form creates a new state of the form and maintains the integrity of the model (in the component).

  • Reactive forms: Easily unit testable as data, or the form state is predictable and can be easily accessed through the component.

Getting Started with Reactive Forms

In the Superhero application, SuperheroesMaterialDesignModule’s components have Angular Material dependencies.

To start using reactive forms, import ReactiveFormsModule to SuperheroesMaterialDesignModule (see Listing 9-14 lines 1 and 7).
1. import { ReactiveFormsModule } from '@angular/forms';
2. @NgModule({
3. declarations: [
4.   // Removed code for brevity
5. ],
6. imports: [
7.     ReactiveFormsModule
8. ],
9. exports: [
10.   ],
11. providers:[HitCounter]
12. })
13. export class SuperheroesMaterialDesignModule { }
Listing 9-14

Import ReactiveFormsModule

Create a new component to experiment with reactive forms. We will build the same create-superhero component as a reactive form. To do this, run the Angular CLI command shown in Listing 9-15.
ng g component superheroes-material-design/create-superhero-reactive-form
Listing 9-15

Create a Component to Demonstrate reactive forms with Angular CLI

Reactive forms are model driven, which is part of the component class. It is bound to the component template.

Create a Form Control with Form Builder

A form control represents a form field. An instance of a form control is created in the Angular component’s TypeScript file. It could represent a form field like a text field, a drop-down, or a radio button.

FormBuilder is an Angular service in the @angular/forms Angular monorepo. It simplifies creating form controls in Angular’s reactive forms.

Reactive forms are model driven. We will create a model in the TypeScript class. Let’s create it by adding the name field (Superhero name). The name field is a text field in the HTML template. It is an instance of the FormControl class in the model.

Listing 9-16 imports and injects FormBuilder. The FormBuilder instance creates a form control representing the name of the superhero. It creates an instance of FormControl by using a helper function named control. The control function initializes the form control with a value. In Listing 9-16, we initialize it with an empty string (see line 11).
1. import { Component, OnInit } from '@angular/core';
2. // Import FormBuilder and FormControl
3. import { FormBuilder, FormControl } from '@angular/forms';
4. @Component({
5. selector: 'app-create-superhero-reactive-form',
6. templateUrl: './create-superhero-reactive-form.component.html',
7. styleUrls: ['./create-superhero-reactive-form.component.css']
8. })
9. export class CreateSuperheroReactiveFormComponent implements OnInit {
10. // Create an instance of FormControl using the FormBuilder
11. name: FormControl = this.fb.control("");
12. // Inject FormBuilder
13. constructor(private fb: FormBuilder) { }
14. ngOnInit() {
15. }
16. }
Listing 9-16

Using FormBuilder to Create a FormControl

In the HTML template, we use a formControl directive to refer to the model object from TypeScript class (see line 3 in Listing 9-17).
1.   <mat-form-field appearance="outline">
2.     <mat-label>Superhero Name</mat-label>
3.     <input type="text" name="name" [formControl]="name" matInput
4.      placeholder="A name to be remembered" />
5.     <mat-hint>Enter name</mat-hint>
6.   </mat-form-field>
Listing 9-17

Using formControl Directive to Refer to the Model (in TypeScript Class)

Note

Using FormBuilder is not mandatory. We may directly create a FormControl instance with a new keyword as well. As you will see in upcoming sections, FormBuilder provides syntactic sugar that simplifies creating reactive forms. Listing 9-18 uses a new keyword to create an instance of FormControl.

export class CreateSuperheroReactiveFormComponent implements OnInit {
// Create an instance of FormControl using the FormBuilder
 name: FormControl = new FormControl("Chhotta Bheem by default");
// Inject FormBuilder
 constructor(private fb: FormBuilder) { }
ngOnInit() {
 }
}
Listing 9-18

Create FormControl with New Keyword

The constructor for FormControl is initialized with a default value, “Chhotta Bheem by default”. The result is shown in Figure 9-7. The form shows the default value.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig7_HTML.jpg
Figure 9-7

FormControl initialized with a new keyword

Capture Changes to the Form Control

Forms are built for users to enter data or select values. Users are expected to make a series of changes in the form controls. When users input data, it is never a one-time activity. User interactions are a stream and a series of changes.

Angular applications use an observable to stream changes. In this case, changes made by the user are streamed by the observable. In the FormControl instance, access the observable with valueChanges (see Listing 9-19).
 ngOnInit() {
   this.name.valueChanges.subscribe( result => console.log(result));
 }
Listing 9-19

Observable for Changes Made to the Text Field

Listing 9-19 subscribes to the observable and prints the result values to the console (see Figure 9-8). It is just a sample to demonstrate the observable. A real-world example might transform the data and send it to a remote service on a server.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig8_HTML.jpg
Figure 9-8

Result logged by the observable

Access a Snapshot

The preceding sample accesses an observable; however, it is possible to access a single value. Use the change handler in the text field. The form control provides a snapshot of the value at that point in time. It is different from a stream, as you saw in the previous sample (see line 5 in Listing 9-20).
1.   <mat-form-field appearance="outline">
2.     <mat-label>Superhero Name</mat-label>
3.     <!-- Change handler on the input field below -->
4.     <input type="text" name="name" [formControl]="name" matInput
5.       (change)="changeHandler()"
6.       placeholder="A name to be remembered" />
7.     <mat-hint>Enter name</mat-hint>
8.   </mat-form-field>
Listing 9-20

Template with Change Handler Attached to the Form Field

The changeHandler function is invoked on a change to the text field. The snapshot of the FormControl field is available in the value field. The code sample logs the snapshot to the console.
changeHandler(){
   console.log("Log the snapshot at a point in time, ", this.name.value);
 }
Listing 9-21

Change Handler Function Invoked on Change

Figure 9-9 shows the result.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig9_HTML.jpg
Figure 9-9

A snapshot value of the text field logged to the browser console

Set a Value on FormControl

In the prior example, a value set by the user in a form control is read by the Angular TypeScript code component. We initialized the form control with a default value; however, we did not set a value.

Imagine that a value needs to be set on the form control when a server-side API returns a value. To mimic the scenario, and without complicating the sample with a server-side API call, we use setTimeout. This is just for the sample. We set a value on the form control after a stipulated number of milliseconds.

To set a value on a form control, use the setValue API (see Listing 9-22).
 ngOnInit() {
   setTimeout( () => this.name.setValue("Value set on timeout"), 1000);
 }
Listing 9-22

Set a Value on a Form Control

The sample times out after a second (1000 milliseconds). this.name is an instance of FormControl. It sets a sample string, “Value set on timeout”, which is set on the text field in the form.

Create a Form Group with Form Builder

In the prior example, a single FormControl instance was created (superhero name). This approach works well for a small number of form controls. As the form grows larger, to represents a group of controls use Form Group. When using a form group, actions like reset can be performed at the form level. Instead of reading the values on the form from separate variables, they can be read on a form-level object.

Let’s continue with the Create Superhero component; for the complete superhero form, create FormGroup. Use FormBuilder instance’s helper function, group, to create a form group. We provide a JSON object with key/value pairs to represent the model object or the form with all the fields required to create a superhero. Listing 9-23 creates a FormGroup instance and assigns it to a class-level field—superheroFormGroup (see lines 3 and 7 to 14).
1.export class CreateSuperheroReactiveFormComponent implements OnInit {
2. // Class variable representing FormGroup.
3. superheroFormGroup: FormGroup;
4. // Inject Form Builder
5. constructor(private fb: FormBuilder) { }
6. ngOnInit() {
7.   // Create FormGroup object with FormBuilder.
8.   this.superheroFormGroup = this.fb.group({
9.     name:",
10.     email:",
11.     details: ",
12.     powers: ",
13.     country: "
14.  });
15. }
16. }
Listing 9-23

Create FormGroup with FormBuilder

Each field name—email, details, and so forth—is initialized with empty an string. We can specify default values to initialize.

In the HTML template, identify the form as a form group and the individual form controls in it as child elements. Listing 9-24 is the complete HTML template for the form. Note the highlighted formGroup directive in the form element (line 1). Each form control is identified with the formControlName attribute in the input elements (see lines 6, 14, 21, 29, and 46).
1. <form #superheroForm="ngForm" [formGroup]="superheroFormGroup">
2. <div>
3.   <!-- Appearance: Outline -->
4.   <mat-form-field appearance="outline">
5.     <mat-label>Superhero Name</mat-label>
6.     <input type="text" name="name" formControlName="name" matInput
7.       placeholder="A name to be remembered" />
8.     <mat-hint>Enter name</mat-hint>
9.   </mat-form-field>
10. </div>
11.  <div>
12.   <mat-form-field appearance="outline">
13.     <mat-label>Email</mat-label>
14.     <input type="text" name="email" formControlName="email" matInput />
15.     <mat-hint>How do I contact the superhero for help?</mat-hint>
16.   </mat-form-field>
17.  </div>
18.  <div>
19.   <mat-form-field appearance="outline">
20.     <mat-label>Powers</mat-label>
21.     <textarea name="details" formControlName="details" cdkTextareaAutosize matInput></textarea>
22.     <mat-hint>Explain superhero powers</mat-hint>
23.   </mat-form-field>
24.  </div>
25. <div>
26.   <!-- Appearance: Outline -->
27.  <mat-form-field appearance="outline">
28.    <mat-label>Country</mat-label>
29.    <mat-select formControlName="country" placeholder="select country" >
30.      <mat-option>None</mat-option>
31.      <mat-optgroup label="Earth">
32.        <mat-option value="in">India</mat-option>
33.        <mat-option value="us">United States</mat-option>
34.      </mat-optgroup>
35.      <mat-optgroup label="Outer Space">
36.        <mat-option value="os">Asgard</mat-option>
37.       </mat-optgroup>
38.     </mat-select>
39.   </mat-form-field>
40.   <!-- <strong>Superhero comes from " {{country}} "</strong> -->
41.  </div>
42. <div>
43.   <!-- Appearance: Outline -->
44.   <mat-form-field appearance="outline">
45.     <mat-label>Special Powers</mat-label>
46.     <mat-select name="powers" formControlName="powers" multiple>
47.       <mat-option>None</mat-option>
48.       <mat-option value="fly">fly</mat-option>
49.       <mat-option value="hammer">wield hammer</mat-option>
50.       <mat-option value="power">grow powerful</mat-option>
51.     </mat-select>
52.   </mat-form-field>
53.   <!-- <strong> My superhero can " {{powers}} "</strong> -->
54. </div>
55. <div class="pull-right">
56.   <button mat-stroked-button> Cancel </button>
57.   <button mat-stroked-button> Save </button>
58. </div>
59. </form>
Listing 9-24

Form Group and Form Controls in the HTML Template

Note that the Form Builder is again syntactic sugar that simplifies creating the form control and the form group. The same code could be written to create an instance of FormGroup that combines multiple FormControl objects instantiated using the new keyword (see Listing 9-25).
ngOnInit() {
   this.superheroFormGroup = new FormGroup( {
     name: new FormControl(“),
     email:new FormControl(“),
     details: new FormControl(“),
     powers: new FormControl(“),
     country: new FormControl(“)
   });
}
Listing 9-25

Create Form Group Object with a Collection for FormControl Objects Instantiated with the New Keyword

Read Form Group Values as a Stream

Before we read the values in the form group, let’s create a model object representing the form. Listing 9-26 is a Superhero form. The model class refers to each control in the form group.
export class Superhero {
   name: string;
   email: string;
   details: string;
   country: string;
   specialPowers: Array<string> = [];
}
Listing 9-26

Model Object Referring to the Form Group

Similar to the form control, we can subscribe to changes to the form group. Angular applications use an observable to stream changes. This observable streams changes made by the user. In the form group, you can access the observable with valueChanges. In Listing 9-27, see lines 11 to 16.
1. ngOnInit() {
2.  // Create FormGroup object
3.   this.superheroFormGroup = this.fb.group({
4.     name:”,
5.     email:”,
6.     details: “,
7.     powers: “,
8.     country: “,
9.     favFood: “
10.   });
11.   // Subscribe to the changes to form group
12.   this.superheroFormGroup
13.   .valueChanges
14.   .subscribe(item =>
15.    console.log("Stream as form changes, ", item as Superhero));
16. }
Listing 9-27

Observable for Changes Made to the Form Group

Listing 9-27 subscribes to the observable and prints the result values to the console (see the result in Figure 9-10). Considering it is a stream, a series of values are printed on the console for each change. It is just a sample to demonstrate the observable. A real-world example might transform the data and send it to a remote service on a server.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig10_HTML.jpg
Figure 9-10

Changes to the form group are streamed. The handler prints the change on browser console

Also notice that the subscribe success function handler receives each change. The data is available on a variable named item. The received form group value instance is type cast to the Superhero class (see line 15 in Listing 9-27).

Read Form Group Values as a Snapshot

Similar to form control, form group values can also be read as a snapshot. You can use the submit event on the form. It provides a snapshot of the form and all of the controls’ values when the user clicks submit.

See the changes to the template file to add a handler in the form submit (see Listing 9-28 and Listing 9-29).
<form #superheroForm="ngForm" [formGroup]="superheroFormGroup" (submit)="submitHandler()">
</form>
Listing 9-28

Form Submit Handler Added

Refer to the contents of the handler function. It prints a snapshot of the form group. The snapshot is available on value variable. As in the previous section, we type cast snapshot to the Superhero model object.
 submitHandler(){
   let superhero = this.superheroFormGroup.value as Superhero;
   console.log("Superhero model object ", superhero);
 }
Listing 9-29

Print Snapshot of the Form Group to the Console

The result is shown in Figure 9-11. It prints a snapshot of the form group to the browser console.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig11_HTML.jpg
Figure 9-11

Print snapshot of a form group

Form Validation

Consider the validators in reactive forms (see Listing 9-30). They are available out of the box to use along with form fields. They ensure the validity and correctness of the form and the data entered by the user.

To use validators in a reactive form, import the validator from the @angular/forms monorepo.
import { Validators } from '@angular/forms';
Listing 9-30

Import Validator

Required

When creating the form control, qualify mandatory field as required. The required validator is available out of the box in validators.

You have seen two ways to create form controls: Form Builder and the new keyword. Listing 9-31 uses the required validator.
// 1. If FormControl constructor is used, pass the validator as the second parameter
name= new FormControl(", [Validators.required])
// 2. With FormBuilder, provide validator as the subsequent object to the initial value in the array
this.superheroFormGroup = this.fb.group({
  name:[", Validators.required],
  // more form controls on the form group
});
Listing 9-31

Using a Required Validator

In the form control constructor (line 1), the second parameter is an array. If there are multiple validations in the form control, provide all validators in the array.

Email

Add the email validator to ensure that the provided text is an email. Add the email validator to the email form control. In Listing 9-32, the form is invalid without a valid email address in the [email protected] format.
// 1. If FormControl constructor is used, pass the email validator as the second parameter
    this.superheroFormGroup = new FormGroup( {
      name: new FormControl(", Validators.required),
      email:new FormControl(", [Validators.required, Validators.email]),
    });
// 2. With FormBuilder, provide validator as the subsequent object to the initial value in the array
    this.superheroFormGroup = this.fb.group({
     name:[", Validators.required],
     email:[", Validators.required, Validators.email],
Listing 9-32

Using a Required Validator

The email form control in the example is required, and the email is validated.

Note

When using the new keyword, the form control could be part of form group. Validators still apply. It is a similar scenario with form builder. The form control could be created as part of the group or with the control function.

minLength

Use the minLength validator to ensure that the user provides at least the stipulated number of characters in the field. Listing 9-33 shows minlength applied in the details form field.
// If FormControl constructor is used, pass the minlength validator as the second parameter
      details= new FormControl(", Validators.minLength(5)),
// 2. With FormBuilder, provide validator as the subsequent object to the initial value in the array (see details field below.
    this.superheroFormGroup = this.fb.group({
      name:[", Validators.required],
      email:[", Validators.required, Validators.email],
      details: [", Validators.minLength(5)],
Listing 9-33

Minimum Length Validator

maxLength

Use the maxLength validator to ensure that the user provides, at most, the stipulated number of characters in the field. Listing 9-34 shows the maxlength applied in the details form field.
// 1. To the FormControl constructor, pass the validator
    this.superheroFormGroup = new FormGroup( {
      name: new FormControl(", Validators.required),
      email:new FormControl(", [Validators.required, Validators.maxLength(100)]),
    });
// 2. With FormBuilder, provide validator as the subsequent object to the initial value in the array (see details field below
    this.superheroFormGroup = this.fb.group({
      name:[", Validators.required],
      email:[", Validators.required, Validators.email],
      details: [", Validators.minLength(5), Validators.maxLength(100)],
    });
Listing 9-34

Minimum Length Validator

Show Form Status and Errors

Angular Material components indicate an error when the validation fails. The components use validators to show the error status. Consider the form in Figure 9-12. The name, email, and details fields show an error because their respective validators—required field validation, email format validation, and minimum character length validation—failed. The error is indicated by a red border around the form field.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig12_HTML.jpg
Figure 9-12

Angular Material shows error state. Field has a red border around the field

Note

The error is shown only after the form field is touched (receives a keyboard focus).

As you have seen with template-driven forms, a field can be in one of the following states.

Valid/Invalid

If a form field validator condition is met, it is valid. Consider an example with an email validator. If the user provided a valid email in the [email protected] format, it is valid; otherwise, it is invalid.

To access the field and the validity status, see Listing 9-35 and Listing 9-36.
 get email(){
   return this.superheroFormGroup
     .get("email");
 }
Listing 9-35

Getter for Email Field

Remember, email is part of a form group. Access the form field using the get function in the form group. Writing a getter function at the component level makes the code clean and easier to access the form field.

Use the getter to access valid and invalid fields (see Listing 9-36).
   <div *ngIf="email.invalid" class="error-message">
     Invalid email. Use [email protected]
   </div>
Listing 9-36

Access Invalid Status

The template checks if the form field is invalid before showing the error message. Note that this code shows the error message the moment the form loads, which is not ideal. We will fix this in the next section.

We may also use the valid status field, if required. Listing 9-37 shows an indicator, which is a Material Design icon that shows that the validation was successful.
1.   <mat-form-field appearance="outline">
2.     <mat-label>Email</mat-label>
3.     <input type="text" name="email" formControlName="email" matInput />
4.     <mat-icon *ngIf="email.valid" matSuffix >check_circle</mat-icon>
5.     <mat-hint>How do I contact the superhero for help?</mat-hint>
6.   </mat-form-field>
Listing 9-37

Access Valid Status

Figure 9-13 shows the result.
../images/475625_1_En_9_Chapter/475625_1_En_9_Fig13_HTML.jpg
Figure 9-13

Indicate valid status with a Material Design icon

Note

Listing 9-37’s line 4 uses the mat-icon component in Angular Material. In an earlier code sample, we imported MatIconModule into SuperheroesMaterialDesignModule. Import it now, if not done already.

touched/untouched

If the user brought keyboard focus onto the form field, it is touched; otherwise, it is untouched. Use the touched and untouched fields in the form field.

Typically, when showing form validation status messages, just checking the valid/invalid status in a form field is not effective. After the user attempts to enter a value, it is logical to show or hide an error message. When the form loads, most fields are blank or preloaded with the current data. The form code and the user do not yet have control.

In Listing 9-38, we modify the code in Listing 9-38 to show the error message only if the field is touched.
   <div *ngIf="email.touched && email.invalid" class="error-message">
     Invalid email. Use [email protected]
   </div>
Listing 9-38

Access in-Valid Status

pristine/dirty

If the user attempted to edit a field, it is marked as dirty; otherwise, it is pristine (either blank or with the default data). A common use case for these flags is to enforce users to save the form data if any field is dirty. You may show an alert that indicates the data might be lost if the user navigates away without saving. When all fields are pristine, such an error does not need to be shown in listing 9-39 and Figure 9-14.
     <strong>
       Pristine: {{ email.pristine }}. Dirty: {{ email.dirty }}
     </strong>
Listing 9-39

Print Pristine or Dirty Status

../images/475625_1_En_9_Chapter/475625_1_En_9_Fig14_HTML.jpg
Figure 9-14

Print pristine or dirty status. This is just to showcase the flag

Validator Error Flags

We have used valid/invalid, touched/untouched, pristine/dirty flags to get a form field status at a point in time. For each validator, Angular adds error flags as well.

Remember the email flag in Listing 9-39. The form control was created with two validators: required and email.
     email:[", Validators.required, Validators.email],

If either of these validators return false, an error flag is added. It is added to the errors object in the form control.

In Listing 9-40, separate error flags provide the ability to show specific error messages. If the required field is empty, specify that it is a required field. If the email format failure occurs, show that the email needs to follow the provided format. See Figure 9-15.
   <div *ngIf="email.errors.required" >
     *Email is a required field
   </div>
   <div *ngIf="email.errors.email" >
     *Invalid email. Use [email protected]
   </div>
Listing 9-40

Show Error Message Based on the Error Flag

../images/475625_1_En_9_Chapter/475625_1_En_9_Fig15_HTML.jpg
Figure 9-15

Show specific error messages based on the error

Conclusion

Angular provides two ways to build forms. This chapter discusses both approaches: template-driven forms and reactive forms. Template-driven forms are easy to build, and use ngModel and two-way data binding. Template-driven forms are mutable. Reactive forms are sophisticated and best fit for complex forms.

In this chapter, we imported the Angular module for template-driven forms, FormsModule. We used Angular Material components to build the form. We used two-way data binding, primarily using the ngModel directive. Form data in the template was matched with the model object in the TypeScript class component.

Some of the built-in form validation features were discussed. These validations were performed on the client in the browser’s Angular code. Hence, they are quick and validate data correctness.

Angular Material component features and Angular form features to show errors when validation fails were also discussed.

Exercise

Enhance the dinosaur form created in Chapter 8. Allow the user to provide the following input.
  • Dinosaur name: Build a text field with standard appearance.
    • Ensure the name is a required field. The name should have at least four characters.

  • Dinosaur family: Build a drop-down with the following values.
    • Abelisauridae

    • Noasauridae

    • Megalosauridae

  • Timeframe: Build a text field and a postfix with Mil-yrs-ago (Millions of years ago)
    • Add a validation for the minimum and maximum numbers. Make it a required field.

  • Dinosaur description: Build a text area with a minimum of 6 rows and a maximum of 20 rows.

  • Continents they lived in: Use a chip list that can accept dynamic values from the user.

Reference

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.165.247