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.
Import Forms Module
Approach to Building Forms
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.
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.
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.
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.
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.
Getter Function Printing Model Object
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.
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.
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
Template-driven Forms, Validators
Using the State Field in Form Controls
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.
In the default theme, floating labels, the cursor, and the border are marked red.
Input Element with Template Reference Variable Assigned with ngModel
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.
Show a Friendly Error Message to the User
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
Print Form Status
Submit Disabled Unless the Form Is Valid
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
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.
Import ReactiveFormsModule
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.
Using FormBuilder to Create a FormControl
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.
Create FormControl with 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.
Observable for Changes Made to the Text Field
Access a Snapshot
Template with Change Handler Attached to the Form Field
Change Handler Function Invoked on Change
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.
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.
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.
Form Group and Form Controls in the HTML Template
Create Form Group Object with a Collection for FormControl Objects Instantiated with the New Keyword
Read Form Group Values as a Stream
Model Object Referring to the Form Group
Observable for Changes Made to the Form Group
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.
Form Submit Handler Added
Print Snapshot of the Form Group to the Console
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.
Import Validator
Required
When creating the form control, qualify mandatory field as required. The required validator is available out of the box in validators.
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.
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
Minimum Length Validator
maxLength
Minimum Length Validator
Show Form Status and Errors
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.
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.
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.
Access Valid Status
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.
Access in-Valid Status
pristine/dirty
Print Pristine or Dirty Status
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.
If either of these validators return false, an error flag is added. It is added to the errors object in the form control.
Show Error Message Based on the Error Flag
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
- 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
Angular Material documentation (https://material.angular.io/)