11

Building Reactive Forms

In the previous chapter, we have already learned how to structure our Angular application at the module and component level, which promotes the maintainability of code, especially in enterprise applications. We have organized modules into three categories: core modules, shared modules, and feature modules. We have also grouped components into two classifications: Smart and Dumb components, which separate components that retrieve data and have dependencies from components that are for presentation purposes only.

We have also discussed how to configure and implement Angular Material, which is a UI library that provides ready-to-use components and base styling for our Angular application.

In this chapter, we will now start learning how to build forms using reactive forms in Angular. We will understand form groups, form controls, and form arrays and create validations in our form.

In this chapter, we will cover the following topics:

  • Understanding reactive forms
  • Basic form controls
  • Grouping form controls
  • Using the FormBuilder service to generate controls
  • Validating form input

Technical requirements

Here is a link to the finished version of this chapter:

https://github.com/PacktPublishing/Spring-Boot-and-Angular/tree/main/Chapter-11

Understanding reactive forms

One of the advantages of the Angular framework is that it already provides its form extensions. We can find these extensions under the @angular/forms package once we have created our Angular application. There are two available ways to build forms. These are template-driven forms and reactive forms; them having their own form extension is advantageous to the developers as this does not require installing under packages to create forms.

At the same time, we can make sure that every Angular application uses a single library for building forms. In this section, we will be focusing more on how to implement reactive forms in our application as this is the commonly used method in developing forms in Angular applications, but first, let’s discuss a basic introduction to the template-driven approach before proceeding to reactive forms.

The template-driven approach

Template-driven forms, as the name suggests, are forms declared and validated on the template (HTML). It uses the ngForm directives, which transforms the HTML form into a template-driven form and creates a top-level FormGroup, and the ngModel directive makes a FormControl for the form elements.

To use template-driven forms, we must import FormsModule into the module where we want to use the template-driven forms. In the following code example, we have imported FormsModule into the app.module.ts file:

…
import { FormsModule } from '@angular/forms';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We must not forget to import FormsModule as our application will not recognize the ngForm and ngModel directives.

Creating a template-driven form

The first step in creating template-driven forms is to create an HTML form template. Let’s have a look at the following code example for an illustration of how to do this:

<form>
  <p>
    <label for="email">Email </label>
    <input type="text" id="email" name="email">
  </p>
  <p>
    <label for="firstname">First Name</label>
    <input type="text" id="firstname" name="firstname">
  </p>
  <p>
    <label for="lastname">Last Name</label>
    <input type="text" id="lastname" name="lastname">
  </p>
  <button type="submit">Submit</button>
</form>

In the preceding code example, we have created our HTML form template and have added three form elements: the email, first name, and last name input, which will be our form controls. We have also enclosed the elements with a <form> tag.

After successfully creating an HTML form template, this form will be automatically converted into a template-driven form. It is not required for us to add the ngForm directive to the form tag as Angular finds all form tags in our application to convert it into a template-driven form, although we can still use the ngForm directive to be assigned in a local template variable for us to access the properties and method of the ngForm directive. We can also use the variable template for submitting our forms. Let’s have a look at the following code example:

<form #userForm="ngForm">

Now, we can convert our elements into form controls by adding the ngModel directive to each input; this allows us to track the values, validation status, and user interaction of each form element. Let’s have a look at the following code example with the added form controls:

<form #userForm="ngForm">
  <p>
    <label for="firstname">First Name</label>
    <input type="text" name="firstname" ngModel>
  </p>
  <p>
    <label for="lastname">Last Name</label>
    <input type="text" name="lastname" ngModel>
  </p>
  <p>
    <label for="email">Email </label>
    <input type="text" id="email" name="email" ngModel>
  </p>
</form>

Lastly, we will add an ngSubmit event to submit the data of the form component. We will add the ngSubmit event to the form tag and add a method to the component class to receive the data. Let’s have a look at the following code example:

<!—HTML template -- >
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<!—typescript file (Component class) -- >
onSubmit(contactForm) {
    console.log(userForm.value);
 }

In the preceding code example, once the user has clicked the Submit button of the form, this will call the onSubmit() method, and it will display the form control values as a JSON object in our console; this will now allow us to use the form values in sending data implementing business logic.

After successfully implementing all the steps, we will now have a final template for the template-driven form:

<form #userForm="ngForm"(ngSubmit)="onSubmit(userForm)">>
  <p>
    <label for="firstname">First Name</label>
    <input type="text" name="firstname" ngModel>
  </p>
  <p>
    <label for="lastname">Last Name</label>
    <input type="text" name="lastname" ngModel>
  </p>
  <p>
    <label for="email">Email </label>
    <input type="text" id="email" name="email" ngModel>
  </p>
 <button type="submit">Submit</button>
</form>

When to use template-driven forms

Template-driven forms are very flexible and easy to implement in Angular applications. However, this approach has some limitations and can cause an impact in terms of maintainability; some of the best scenarios for using a template-driven approach in building forms are set out here:

  • It’s easier to use template-driven forms when migrating from AngularJS to Angular2, such that both use the ngModel directive.
  • Template-driven forms are more suitable in simple and small forms that do not require complex validations since validation is applied at the template level. This could be a disadvantage as it will be hard to maintain validations on larger applications at the same time. It has limitations on applying validations to the form controls.

In the second of the aforementioned scenarios, reactive forms are chosen over template-driven forms as complex forms can be handled better with reactive forms, especially in implementing validations. Let’s now understand the concept of reactive forms.

The reactive approach

A reactive form is the second approach in building forms in Angular applications; this is the most commonly used approach as it is more effective in handling complex forms than template-driven forms. Reactive forms are also known as model-driven forms, in which we define the structure of our forms in the component class instead of defining it in the template.

We also define the validations in the class before we bind it in to our HTML form, which means that the logic and validation patterns will now be separated from the HTML template and will be maintained by the TypeScript side of our component.

Using reactive forms

The first step for us to use reactive forms is to import ReactiveFormsModule; this is usually imported into the root module or the shared module of the application. ReactiveFormsModule contains all directives—such as formGroup and formControlName—that will allow us to implement reactive forms; this is also found under the @angular/forms package.

After successfully importing ReactiveFormsModule, the next step is to create our HTML form template and create a model using FormGroup, FormControl, and FormArray. These are the three building blocks of reactive forms that we will use to bind our form templates and are outlined in more detail here:

  • FormControl: This represents a single form element inside a form; it stores the value of a form element that allows us to retrieve data from each input.
  • FormArray: This is a collection of form controls that allows us to dynamically add and remove controls to accept more values from the form.
  • FormGroup: This is a collection of form controls; it can also contain another form group or form arrays.

Assuming we have a HeroesComponent, we will create a FormGroup by writing the following code in the class component:

userForm = new FormGroup({})

In the preceding code example, we have instantiated a new FormGroup and assigned it to the userForm variable; this is only a form group, and we have not yet added form controls to the model. To add a form control, we will place the following code:

userForm = new FormGroup({
  email: new FormControl(),
  firstName: new FormControl(),
  lastName: new FormControl(),
});

We can see in the preceding example that we have added three form controls to our FormGroup; this can now be bound to the HTML form template in our application to capture the values and state of form elements.

Let’s now create an HTML form template with formGroup and formControlName directives:

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
   <p>
    <label for="email">Email </label>
    <input type="text" id="email" name="email"
      formControlName="email">
  </p>
  <p>
    <label for="firstname">First Name </label>
    <input type="text" id="firstname" name="firstName"
      formControlName="firstname">
  </p>
  <p>
    <label for="lastname">Last Name </label>
    <input type="text" id="lastname" name="lastname"
      formControlName="lastName">
  </p>
  <p>
    <button type="submit">Submit</button>
  </p>
</form>

In the example code, we can see that the template is almost the same as the template-driven forms. The only difference is that we use formGroup and formControlName directives to bind our form. The formGroup directive is used to bind the userFrom form group in our component class; on the other hand, the formControlName directive is used to bind the values and the state of the form controls defined in the userForm form group. Lastly, we are still using the ngSubmit event to call a method when the Submit button in the form is clicked.

We have now successfully created a reactive form in our application, but this only covers the basic features and concepts of reactive forms. In the following sections of this chapter, we will be discussing the functionalities and capabilities of form controls and form groups.

Basic form controls

This section will now discuss more of the concepts of form controls in reactive forms. We have already created an example of form controls in the previous section, but now, we will discover more about the functions and capabilities of form controls in Angular.

Form controls represent a single form element inside a form; they store the value of a form element that allows us to retrieve data of each input. This can be input, textarea, or any element that accepts values. When used in Angular, form controls can be instantiated by adding new FormControl('') code; we can see that it takes a single argument that defines the values of the control. These values can be null as form controls can be reset.

Form controls are like the properties of a JSON object, but compared to JSON, each control has its methods that will help us control, modify, and validate the values.

Next, let’s have a look at the different methods and features of form controls.

Form control methods

Let’s have a look at the different form control methods and their parameters that we can use for modifying controls:

  • setValue(): A method that sets the new value for the control.

Parameters:

  • value: The new value assigned to the form control.
  • options: An object that defines the configuration of the controls on how it will propagate changes and emit events when the value changes. Here are the options that we can set in the form control:
    • onlySelf: When set to true, the changes from the control will not affect the other controls.
    • emitEvent: When set to true or not supplied, statusChanges and valueChanges observables are emitted when the status and the value of the form control are updated.
    • emitModelToViewChange: When set to true or not supplied, a change from the form control will call the onChange event to update the view.
    • emitViewToModelChange: When set to true or not supplied, a change from the form control will call the onModelChange event to update the view.

Here’s the code for using the setValue() method:

setValue(value: TValue, options?: { onlySelf?: boolean; emitEvent?: boolean; emitModelToViewChange?: boolean; emitViewToModelChange?: boolean; }): void
  • patchValue(): Patches the value of a control. The parameters of the patchValue method are the same as the setValue() method.

Here’s the code for using the patchValue() method:

patchValue(value: TValue, options?: { onlySelf?: boolean; emitEvent?: boolean; emitModelToViewChange?: boolean; emitViewToModelChange?: boolean; }): void
  • getRawValue(): Retrieves the value of a form control. This is commonly used on disabled form controls.

Here’s the code for using the getRawValue() method:

getRawValue(): TValue
  • reset(): Resets the form control from its default value. It will also mark the control as pristine and untouched.

Parameters:

  • formState: Defines the initial value and the disabled state of the control.
  • options: An object that defines the configuration of the controls on how it will propagate changes and emit events when the value changes. We can set the following option in the form control:
    • onlySelf: When set to true, changes from the control will not affect the other controls.

Here’s the code for using the reset() method:

reset(formState?: TValue | FormControlState<TValue>, options?: { onlySelf?: boolean; emitEvent?: boolean; }): void
  • registerOnChange(): Registers a listener to emit events once the form control value is changed.

Parameters:

  • function: The method that is called when the value changes, as illustrated here:
    registerOnChange(fn: Function): void
  • registerOnDisabledChange(): Registers a listener to emit events once the isDisabled status of the control changes.

Parameters:

  • function: The method that is called when the disabled status changes, as illustrated here:
    registerOnDisabledChange(fn: (isDisabled: boolean) => void): void

We have now learned about the different methods we can use in form controls. Now, let’s have a look at some examples of different usage of form controls.

Initializing form controls

There are several ways to initialize our form controls. We can set the value, the disabled state, and the validators of a specific form control. Let’s have a look at the following examples:

  • Initializing a form control with an initial value:
    const control = new FormControl('Hero!'); console.log(control.value); // Hero

In the preceding code example, we instantiated a form control with a default value of Hero. We can access the value by accessing the value property inherited from AbstractControl.

  • Initializing a form control with an initial value and the disabled state:
    const control = new FormControl({ value: 'Hero', disabled: true });
    // get the status
    console.log(control.value, control.status); //Hero,
                                                //DISABLED

In the preceding code example, we instantiated a form control with an object value. This initializes the value and the disabled state of the form control. We can access the value by accessing the status property inherited from AbstractControl.

  • Initializing a form control with an initial value and an array of built-in validators:
    const control = new FormControl('', [Validators.email, Validators.required);
    // get the status
    console.log(control.status); // INVALID

We instantiated a form control with an empty string value in the preceding code example. With the second parameter of an array of validators, this will return an invalid status since there should not be an empty value and should be a valid email format.

Resetting form controls

We can use the reset() method to reset the value and the disabled state of a form control. Let’s have a look at the following code examples of different usage:

  • Resetting controls to a specific value:
    const control = new FormControl('Tony Stark')
    console.log(control.value); // Tony Stark
    control.reset('Iron Man');
    console.log(control.value); // Iron Man

In the preceding code example, we have used the reset() method with a parameter. The parameter allows us to reset the form control to a specific value.

  • Resetting controls to an initial value:
    const control = new FormControl('Tony Stark')
    console.log(control.value); // Tony Stark
    control.reset();
    console.log(control.value); // Tony Stark

In the preceding code example, we used the reset() method without a parameter. This would reset the form control’s value with its initial value.

  • Resetting controls with a value and a disabled state:
    const control = new FormControl('Tony Stark'); console.log(control.value); // Tony Stark console.log(control.status); // VALID
    control.reset({ value: 'Iron Man', disabled: true });
    console.log(control.value); // Iron Man console.log(control.status); // DISABLED

In the preceding code example, we have used an object parameter in calling the reset() method, and we have indicated the value and disabled state of the form control. In this case, it will disable the control and change the status to DISABLED.

Listening to events

In using form controls, we can listen to several events such as changing values and status. Let’s have a look at the following code examples on how to listen to events of form controls:

  • Listening to value changes:
    control = new FormControl('');
    this.control.valueChanges.subscribe((data) => {
          console.log(data); // Iron Man
        });
    this.control.setValue('Iron Man')

In the preceding code example, we have called the valueChanges property that has an Observable type, which we can subscribe to listen to changes to the form control value. In this case, once we set the value of the form control, the valueChanges property will emit the new value.

  • Listening to status changes:
    control = new FormControl('');
    this.control.statusChanges.subscribe((data) => {
          console.log(data); // DISABLED
        });
    This.control.disable ()

In the preceding code example, we have called the statusChanges property that has an Observable type, which we can subscribe to listen to changes to the form control status. In this case, once we disable the form control, this will emit the new status, which is DISABLED.

We have already learned about the features and functionalities of form controls; now, we will discuss how to group form controls using form groups and form arrays.

Grouping form controls

This section will now discuss how to group form controls in our application. Forms contain several related controls, which is why it is necessary to group them for a better structure. Reactive forms provide two ways to group form controls, as follows:

  • Form group: Creates a form with a fixed set of form controls. Form groups can also contain another set of form groups to handle complex forms.
  • Form array: Creates a form with dynamic form controls. It can add and remove form controls and at the same time can contain other form arrays to handle complex forms.

Creating form groups

Form groups allow us to control the values and status of form controls by groups. We can also access a single form control inside a form group using its name. To create a form group, let’s follow the next steps:

  1. Let’s say we have a HeroComponent; for example, the first step is to import the FormGroup and FormControl classes from the @angular/forms package, like so:
    import { FormGroup, FormControl } from '@angular/forms';
  2. The next step is to create a FormGroup instance. In this example, we want to create a new form group with firstName, lastName, and knownAs form controls:
    export class HeroComponent {
      heroForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
          knownAs: new FormControl('')
    });
    }

In the preceding code example, we have created a new form group named heroForm. Simultaneously, we have added three form controls as object parameters included in the heroForm form.

  1. The next step is to bind our form group instance with the form element in our view:
    <form [formGroup]=" heroForm ">
      <label for="first-name">First Name: </label>
      <input id="first-name" type="text"
        formControlName="firstName">
      <label for="last-name">Last Name: </label>
      <input id="last-name" type="text"
        formControlName="lastName">
      <label for="known-as">Known As: </label>
      <input id="known-as" type="text"
        formControlName="knownAs"> </form>

In the preceding code example, we have used the formGroup directive to bind our heroForm form in our form element. We must also bind each form control with the input elements by using the formControlName directive.

  1. The last step is to get the value of the whole form group. We will use the ngSubmit event to call a method and will retrieve the form value by accessing the value property, like so:
    //hero.component.html
    <form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
    //hero.component.ts
    onSubmit() {
    // Will display value of form group in a form of JSON
     console.warn(this.heroForm.value);
    }

We have created and bound an example form group, but this is only a simple form group and introduces a linear structure of controls. Now, let’s create a form group that contains form groups.

Creating nested form groups

Form groups can also have another form group instead of having a list of controls. Imagine a JSON object that has properties with the value of another JSON object. This cannot be handled by a simple linear of form controls, and we must create another set of form groups to take this kind of object.

Let’s follow the next steps to develop nested form groups:

  1. We will be using the previous form example; in this case, we would want to add a new address property in our form, but instead of having it as a new instance of the form control, we will declare it as a new instance of the form group:
    export class HeroComponent {
     heroForm = new FormGroup({
          firstName: new FormControl(''),
    lastName: new FormControl(''),
    knownAs: new FormControl('')
    address: new FormGroup({
        street: new FormControl('')
        city: new FormControl('')
        country: new FormControl('')
    })
    });
    }

In the preceding code example, we have added an address property as a new form group instance. We have also added new form controls inside the form group—namely, street, city, and country. This is now considered a nested form group.

  1. The next step is to bind the nested form group with our form element in the view:
      <div formGroupName="address">
            <label for="street">Street: </label>
            <input id="street" type="text"
              formControlName="street">
            <label for="city">City: </label>
            <input id="city" type="text"
              formControlName="city">
            <label for="country">Country: </label>
            <input id="country" type="text"
              formControlName="country">
        </div>

In the preceding code example, we have used the formGroupName directive to bind our address form group. Remember that this element should be inside the heroForm form group; we have also used the formControlName directive to bind the controls under the nested form group. Now, we can also use the ngSubmit event again and call the value property as we did in the previous example to get the value of the whole form.

We have created simple and complex forms using form groups. Let’s now discuss another way of grouping controls using form arrays.

Creating form arrays

Form arrays are helpful, especially if we want to add or remove controls in our form at runtime. This allows us to have flexible forms in our application and at the same time handle a more complex set of objects to process. To create a form array, let’s have a look at the following steps:

  1. We will be using the previous form example; in this case, we would want to add a new powers property to our form and declare it as a new FormArray instance:
    export class HeroComponent implements OnInit {
     powerFormArray: FormArray;
     constructor() {
        this.powerFormArray=
          this.heroForm.get("powers") as FormArray;
     }
    ngOnInit() {
        heroForm = new FormGroup({
            ... controls from previous example
            powers: new FormArray([])
       })
     }
    }

In the preceding code example, we have created a new FormArray instance inside our heroForm form group. This accepts an empty array having no form controls on initialization. We have also assigned the instance of the form array into a variable for us to access the array in our view.

  1. The next step is to create methods that can add and remove an instance of a form control in the form array:
     addPower() {
        (this.form.get("powers") as FormArray).push(new
          FormControl());
      }
      deletePower(i: number) {
        (this.form.get("powers") as
          FormArray).removeAt(i);
      }

In the preceding code example, we have created two methods that we will use for the form array. The addPower() method allows us to add a new form control instance in the power form array; this gets the instance of the form array by name and pushes a new form control instance.

On the other hand, the deletePower() method gets the instance of the form array by name and removes a specific form control using the removeAt() method and the index of the control to be deleted.

  1. The last step is to bind the form array instance with the form element in the view:
    <ng-container formArrayName="powers">
       <label for="tags">Tags</label>
       <div class="input-group mb-3" *ngFor="let _ of
         powerFormArray.controls; index as i">
          <input type="text" class="form-control"
             [formControlName]="i" placeholder="Power
              Name">
          <button (click)="deletePower(i)"
            class="btn btn-danger"
            type="button">Delete</button>
    </div>
          <button class="btn btn-info me-md-2"
            type="button" (click)="addPower()">
            Add</button>
    </ng-container>

In the preceding code example, we have bound the powers to form an array with the view using the formArrayName directive. We have also used the ngFor directive to iterate all the controls inside the form array; we would also need to get the index of each control to pass it on to our deletePower() method.

After successfully creating the form arrays, we will now have a view of the form:

Figure 11.1 – Hero form with a form group and form arrays

Figure 11.1 – Hero form with a form group and form arrays

We have successfully created reactive forms using form groups and form arrays. Now, we will use the FormBuilder service to simplify the syntax in creating forms in our application.

Using the FormBuilder service to generate controls

In the previous section, we successfully created reactive forms using form groups, form arrays, and form controls. However, as we can see from the syntax, creating forms becomes repetitive. We are always instantiating new instances of form controls, form arrays, and form groups, and this is not ideal in larger forms. FormBuilder provides the solution for this issue.

This is a service that can be injected into our components to generate groups, controls, and arrays without instantiating new ones. To create a reactive form using FormBuilder, we will be following the next steps:

  1. We will be transforming the form in the previous section using FormBuilder. The first step is to import the FormBuilder service into our component from @angular/forms:
    import { FormBuilder } from '@angular/forms';
  2. The next step is to inject the FormBuilder service into our component:
    export class HeroComponent implements OnInit {
     powerFormArray: FormArray;
    constructor(private fb: FormBuilder) {}
    ... code implementation
    }
  3. The last step is now to create and generate controls using the methods of the FormBuilder service:
    export class HeroComponent implements OnInit {
     heroForm = this.fb.group({
          firstName: [''],
         lastName: [''],
         knownAs: [''],
         address:  this.fb.group({
            street: [''],
            city: [''],
            country: [''],
        }),
          powers: this.fb.array([])
    });
    constructor(private fb: FormBuilder) {}
    ... code implementation
    }

We can see in the preceding example that our form has the same structure as the form we created in the previous section. The major difference is that we are using the methods of FormBuilder to create forms. We have used the group() method to generate form groups, the array() method to generate a form array, and an array with an empty string value to generate a control and set its default value.

The output for this code will be the same. FormBuilder methods are mainly for making our reactive forms clean and readable. Now, we will discuss how to add validations to our controls.

Validating form input

We have now created and simplified our reactive form in the previous section, but we want to make our forms accurate in accepting data and at the same time create a user-friendly experience for the user to let them know easily what the valid values for each control are. Now, we will learn how to add validations to our reactive forms.

In reactive forms, we are adding validators as parameters directly to the form controls in the component class instead of adding them as an attribute in the template.

Built-in validators

Angular provides several built-in validator functions that we can use directly in our forms. Let’s have a look at some of these:

  • static min(min: number)—Requires the value of the control to be equal to or greater than the given number:
    form = this.fb.group({
      name: [10, [Validators.min(4)]]
    });
    console.log(this.form.status) // returns VALID
    static max(max: number) – requires the value of the control to be equal to or less than the given number.
    form = this.fb.group({
      name: [3, [Validators.max (4)]]
    });
    console.log(this.form.status) // returns VALID
  • static required(control: AbstractControl<any, any>)—Controls must not have a non-empty value:
    form = this.fb.group({
      name: ['test value', [Validators.required]]
    });
    console.log(this.form.status) // returns VALID
  • static requiredTrue(control: AbstractControl<any, any>)—Controls must have a value of true:
    form = this.fb.group({
      name: [true, [Validators.requiredTrue]]
    });
    console.log(this.form.status) // returns VALID
  • static minLength(minLength: number)—Used for arrays and strings, this requires that the length of the value should be equal to or greater than the given number:
    form = this.fb.group({
      name: ['test', [Validators.minLength (4)]]
    });
    console.log(this.form.status) // returns VALID
  • static maxLength(maxLength: number)—Used for arrays and strings, this requires that the length of the value should be equal to or less than the given number:
    form = this.fb.group({
      name: ['test', [Validators.maxLength (4)]]
    });
    console.log(this.form.status) // returns VALID

Custom validators

Other than the built-in validators, we can also create custom validators, which is helpful if our forms require more complex verification and checking.

Let’s have a look at the following example custom validator:

import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
export function checkHasNumberValidator(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors |
      null => {
      const error = /d/.test(control.value);
      return error ? {hasNumbers: {value: control.value}} :
        null;
    };
}

In the preceding code example, we have created a new validator named checkHasNumberValidator(). The main use of this validator is to invalidate control values that have a number. We have retrieved the form control where the validator is assigned, then we have tested the value of the control and will return a custom error named hasNumbers if the regex is true.

After successfully creating the custom validator, we can now use it in our controls, like so:

heroForm = this.fb.group({
     firstName: ['', [checkHasNumberValidator]],
     lastName: ['', [checkHasNumberValidator]],
     knownAs: [''],
     address:  this.fb.group({
        street: [''],
        city: [''],
        country: [''],
    }),
      powers: this.fb.array([])
});

In the preceding example code, we want our first name and last name field to be restricted to letters only. In this case, we have used checkHasNumberValidator as a second parameter for the firstName and lastName controls.

Let’s now proceed to the implementation of reactive forms.

Implementing reactive forms in our project

We have now successfully learned how to develop reactive forms using FormBuilder, and at the same time, added validations to our controls. Now, we will implement these reactive forms in our project.

The first step is to create our form group instance. Under the anti-hero/components/anti-hero-form file, we will create our form group using the FormBuilder service in the class component, and at the same time, we will create our form elements in our HTML template. Follow the next steps:

  1. Create a form group instance by executing the following code:
    export class AntiHeroFormComponent implements OnInit {
      @Input() selectedId = "";
      @Input() actionButtonLabel: string = 'Create';
      form: FormGroup;
      constructor(private fb: FormBuilder) {
        this.form = this.fb.group({
          id: [''],
          firstName: [''],
          lastName: [''],
          house: [''],
          knownAs: ['']
        })
       }
    <! – Please refer to the anti-hero-form.component.ts file in the GitHub repo, Thank you ->
    }
  2. Then, create an HTML template, like so:
    <! – Please refer to the anti-hero-form.component.html file in the GitHub repo, Thank you ->

In the implemented code in our component class, the first thing we did was create a form group object. We have added several controls that resemble the properties of the anti-hero object. Our goal here is to use the same form for creating and updating an anti-hero detail. In this case, we have also added several Input() bindings and methods to our class to help the form identify which actions are currently being done:

  • selectedId: This will accept the ID of the anti-hero if the actions are updated.
  • actionButtonLabel: This will change depending on the action being done (Create or Update).
  • checkAction(): If selectedId has a value, this will change the value of actionButtonLabel to "Update".
  • patchDataValues(): This will be used for patching the values of the selected anti-hero in the form controls.
  • emitAction(): Emits the value of the form and the action into the parent component.
  • clear(): Calls the reset() method to clean the form.
  1. The next step is to use the anti-hero form in our form page component. Under the anti-hero/pages/form file, we will place the anti-hero form in the HTML template, and at the same time, check the current route if it has the ID of the selected anti-hero in the parameters. Here are the steps:
    1. Add the anti-hero form to the HTML template:
    <app-anti-hero-form [selectedId]="id"></app-anti-hero-form>
    1. Add the activated router to capture the ID:
    export class FormComponent implements OnInit {
      id = "";
      constructor(private router: ActivatedRoute) { }
      ngOnInit(): void {
        this.id = this.router.snapshot.params['id'];
      }
    }
  2. The next step is now to create a route for our page form component. In the anti-hero-routing.module.ts file, we will add the following entry to our routes:
      {
        path: "form",
        children: [
          {
            path: "",
            component: FormComponent
          },
          {
            path: ":id",
            component: FormComponent
          }
        ]
      },

In the preceding code example, we have created two routes that redirect to FormComponent. The first route is for the create action, which has a baseURL/anti-heroes/form route, and the second route is for the update action, which has a baseURL/anti-heroes/form/:id route. This means that we are using the same components for our two actions, and the id parameters act as our indicator of which action is currently being done.

  1. The last step is to add navigations to the list component. We will add several methods that will call navigate methods to redirect us to the form component depending on the selected action, as follows:
    • list.component.html:
      <!-- Dumb component command bar -->
      <app-anti-hero-command-bar (action)="executeCommandBarAction($event)"></app-anti-hero-command-bar>
      <!-- Dumb component anti hero list -->
      <app-anti-hero-list [antiHeroes]="antiHeroes" (antiHero)="selectAntiHero($event)" [headers]="headers"></app-anti-hero-list>
    • list.component.ts:
      selectAntiHero(data: {antiHero: AntiHero, action: TableActions}) {
      <! – Please refer to the list.component.ts file in the GitHub repo, Thank you->
        this.router.navigate(['anti-heroes', 'form',
                              data.antiHero.id]);
        }
        executeCommandBarAction(action: CommandBarActions) {
          switch(action) {
            case CommandBarActions.Create: {
              this.router.navigate(["anti-heroes", "form"]);
              return;
            }
            case CommandBarActions.DeleteAll: {
              return;
            }
            default: ""
          }
        }

After accomplishing all the steps, we will now have the following form output:

Figure 11.2 – Form UI for creating an anti-hero

Figure 11.2 – Form UI for creating an anti-hero

Summary

With this, we have reached the end of this chapter; let’s have a recap of the valuable things you have learned. You have learned about the concepts and implementation of Angular reactive forms, and we have implemented FormGroup, FormBuilder, and formControlName directives to bind input values to capture data in our form. We have also discussed how to group form controls for binding nested properties and create form arrays in our reactive forms. This is primarily useful if some objects we want to display have array values.

At the same time, we want to accept a list of entries from users. Lastly, we have also learned how to implement validations for form controls to handle and verify user input, which will be beneficial for the user experience and help avoid unexpected errors.

In the next chapter, we will learn about the concepts and implementation of state management in Angular applications; we will discuss the idea of the Redux pattern and the NgRx library in terms of how they can improve the application architecture.

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

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