Angular Reactive Forms - Part Eight

In the last article, we’ve learned about Template driven forms. And here, we’ll look into a different approach to create forms and that is Reactive forms. And this approach gives us more control over the structure and the behavior of our form. If you want to build dynamic forms where input fields are rendered based on some data structure that you get from the server, then the only option is to create this controller objects in the code. And by the way, Reactive forms are easier to unit test. So by the end of this article, we’ll be able to:

  • Create Control objects programmatically
  • Add validation to the form
  • Implement custom validation
  • Implement asynchronous validation (that involves contacting the server)
  • Build forms that include an array of objects that are added or removed dynamically.

But if you’re a beginner in Angular and you want to start your journey with Angular then here is the roadmap for you.

And it is the 8th article of Angular series. So let’s start our journey with Angular.

Building a Bootstrap Form

To speed things up, I’ve built the form for you. Just extract the RAR file and copy paste the component into your project component folder. And then register this component in app.module.ts and call the component with its selector in app.component.html.

Bootstrap Form

So this is our signup-form.

Creating Controls Programmatically

It is the simple bootstrap form and now we want to convert it to the Angular Reactive form. You already know that in the template driven form, we use ngModel directive on input elements and this directive internally creates an instance of FormControl. But now when we’re building the reactive form, we should create this FormControl object explicitly in the code. So let’s go to our signup-form.component.ts and here we’ll import a couple of classes.

  1. import { FormGroup, FormControl } from "@angular/forms"

Next, we need to create an instance of FormGroup, So,

FormGroup

Now if you see this image, we have 3 overloaded constructors of FormGroup. Second and third are optional but the 1st is necessary. It is the object property containing one or more key value pairs, each key is a string and the value is an AbstractControl

Now what is AbstractControl?

Well in OOP, we’ve this concept called inheritance. So if we have multiple classes that have some common behavior and properties, instead of implementing this common behavior in multiple places we define them once in a parent or base class and then those other classes inherit this common behavior from their base class. So in Angular, AbstractControl is the base class and FormControl and FormGroup are child classes. All the properties which you see common in FormControl and FormGroup classes are actually defined in AbstractControl class

AbstractControl

So back in component and as the intellisense suggests.

  1. export class SignupFormComponent {  
  2.   form = new FormGroup({  
  3.     username: new FormControl()  
  4.   });  
  5. }  

And if you’re dealing with a complex form that includes sub groups then you’ll create the instance of FormGroup here instead of FormControl with username. And then this FormGroup contains 1 or more FormControl objects. Let’s come back to the point,

  1. export class SignupFormComponent {  
  2.   form = new FormGroup({  
  3.     username: new FormControl(),  
  4.     password: new FormControl()  
  5.   });  
  6. }  

So this is how we explicitly create FormGroup and FormControl objects in the code. Now you might think that it is the javascript object initializer syntax and here we use the key in string. Yes we can go with string syntax as well, it is valid but if it is a single keyword like here we have username. It is ok if we implement it without single quotes.

Now we need to go to our template and associate our input fields with this FormControl objects. So here back in form template, first we need to apply a directive of our form element.

  1. <form [formGroup]="form">  
  2. </form>  

Here ‘form’ is the component property which we created just above and [formGroup] is because of new FormGroup() as we’re creating the FormGroup instance in form field. Now inside this group, we’ve 2 controls (username and password). Now let’s define these properties in our template with the help of formControName attribute

  1. <form [formGroup]="form">  
  2.     <div class="form-group">  
  3.         <label for="username">Username</label>  
  4.         <input   
  5.             formControlName="username"  
  6.             id="username"   
  7.             type="text"   
  8.             class="form-control">  
  9.     </div>  
  10.     <div class="form-group">  
  11.         <label for="password">Password</label>  
  12.         <input   
  13.             formControlName="password"  
  14.             id="password"   
  15.             type="text"   
  16.             class="form-control">  
  17.     </div>  
  18.     <button class="btn btn-primary" type="submit">Sign Up</button>  
  19. </form>  

Now save the files and open it into the browser.

browser

Now, if you open angular.io and search for formgroupdirective, you’ll see that this directive is defined in ReactiveFormsModule so all the directives for building reactive forms are defined in a separate module called ReactiveFormsModule.

ReactiveFormsModule

So we need to explicitly import into our main module. So let’s go to the app.module.ts

  1. imports: [  
  2.   BrowserModule,  
  3.   FormsModule,  
  4.   ReactiveFormsModule  
  5. ],  

And of course, our auto import plugin automatically adds the import statement on the top.

  1. import { FormsModule, ReactiveFormsModule } from "@angular/forms";  

Now if you go back to the browser you’ll see no error message in the console now. Everything is working perfectly.

Bootstrap Form

Adding Validation

Now let’s add the validation to this form. In the previous article, when we were building template driven forms we were using html5 attributes. But here when we’re building reactive forms we’ll not use these html5 attributes. We assign validators when we’re creating FormControl object.

Adding Validation

First argument is the initial state and the 2nd argument is validator object and its type is ValidatorFn or ValidatorFn[]

So what is ValidatorFn?

In Angular, we’ve a class called Validators. So all the Validators we have in our template driven forms, and they exist here as well. These validators or methods defined in this class. So here we’ve different methods,

  • Validators.required() 
  • Validators.minlength()
  • Validators.maxlength()
  • Validators.pattern()
  • Validators.email()

And obviously, all of these methods are static. So we can access them with the class name.

username: new FormControl('', Validators.required)

Note I’m not calling required() here, we’re simply passing the reference to the constructor function because the 2nd argument for the constructor of FormControl requires validatorFn which is basically a function reference

  1. export class SignupFormComponent {  
  2.   form = new FormGroup({  
  3.     username: new FormControl('', Validators.required),  
  4.     password: new FormControl('', Validators.required)  
  5.   });  
  6. }  

Now we want to add the valdation messages. We already know that we can access the component fields directly in the component html files.

  1. <div class="form-group">  
  2.     <label for="username">Username</label>  
  3.     <input   
  4.         formControlName="username"  
  5.         id="username"   
  6.         type="text"   
  7.         class="form-control">  
  8.     <div *ngIf="form" class="alert alert-danger">  
  9.         Username is Required.              
  10.     </div>  
  11. </div>  

Now form is the instance of FormGroup class and here we’ve the method called get() and from here we can get access to any FormControl object inside this group. So,

  1. <div *ngIf="form.get('username').touched && form.get('username').invalid" class="alert alert-danger">  
  2.     Username is Required.  
  3. </div>  

Now let’s see in the browser if our code is working properly,

Bootstrap Form

Now our code is little bit noisy and we need to cleanup this code. So come back to the component and here we can define the property that gives us access to this username FormControl object. So remember properties, they look like fields from the outside but internally they are methods actually. So here we define getter to call the username()

  1. export class SignupFormComponent {  
  2.   form = new FormGroup({  
  3.     username: new FormControl('', Validators.required),  
  4.     password: new FormControl('', Validators.required)  
  5.   });  
  6.   
  7.   get username() {  
  8.     return this.form.get('username');  
  9.   }  
  10. }  

And now in signup-form.component.html

  1. <form [formGroup]="form">  
  2.     <div class="form-group">  
  3.         <label for="username">Username</label>  
  4.         <input   
  5.             formControlName="username"  
  6.             id="username"   
  7.             type="text"   
  8.             class="form-control">  
  9.         <div *ngIf="username.touched && username.invalid" class="alert alert-danger">  
  10.             Username is Required.  
  11.         </div>  
  12.     </div>  
  13.     <div class="form-group">  
  14.         <label for="password">Password</label>  
  15.         <input   
  16.             formControlName="password"  
  17.             id="password"   
  18.             type="text"   
  19.             class="form-control">  
  20.     </div>  
  21.     <button class="btn btn-primary" type="submit">Sign Up</button>  
  22. </form>  

This is the final code. And if you run the application in the browser, it is working fine.

Different Validation Errors

Now let’s see how to add multiple validators and how to show the different validation error messages in the browser.

  1. export class SignupFormComponent {  
  2.   form = new FormGroup({  
  3.     username: new FormControl('', [  
  4.       Validators.required,  
  5.       Validators.minLength(3)  
  6.     ]),  
  7.     password: new FormControl('', Validators.required)  
  8.   });  
  9.   
  10.   get username() {  
  11.     return this.form.get('username');  
  12.   }  
  13. }  

Now let’s display specific error messages.

  1. <div *ngIf="username.touched && username.invalid" class="alert alert-danger">  
  2.     <div *ngIf="username.errors.required">Username is Required.</div>  
  3.     <div *ngIf="username.errors.minlength">Username should be minimum {{ username.errors.minlength.requiredLength }} characters.</div>  
  4. </div>  

Now let’s test the application. And yes it is working fine.

Implementing Custom Validation

Let’s see how to implement custom validator function. Now come on to the Angular documentation and search for validatorFn. It is an interface as it is indicated with I in search. And if we read the documentation of this function we’ll see that in this interface functions represents with this signature.

Custom Validation

Function that takes AbstractControl as parameter and returns ValidationErrors or null. In the Typescript article, we learned that we use interfaces defined a Shape or object. We can use interfaces to define the shape of function as well. Any function that matches above signature is considered a ValidatorFn. We can define this function anywhere in our application but it is better to put all the ValidatorFn inside a class. This way we can encapsulate them in a single place.

So back in VS Code, We add a new file ‘username.validators.ts’ in signup-form folder. Here we export a class and define the functions which functionality we want.

  1. import { AbstractControl, ValidationErrors } from "@angular/forms";  
  2.   
  3. export class UsernameValidators {  
  4.     cannotContainSpace(control: AbstractControl) : ValidationErrors | null {  
  5.         if ((control.value as string).indexOf(' ') >= 0) {  
  6.             // We should return Validation Errors  
  7.         }  
  8.     }  
  9. }  

Now back again to the Angular documentation and search for ValidationErrors

Validation Errors

And here we see, this ValidationErrors represent an object that has one or more key value pairs, keys are string and values can be anything. So in our case,

  1. if ((control.value as string).indexOf(' ') >= 0) {  
  2.     return { cannotContainSpace}  
  3. }  

And for another scenario, if we are going to implement that built in required validator, the key here would be required.

  1. if ((control.value as string).indexOf(' ') >= 0) {  
  2.     return { required }  
  3. }  

To come back to the point; so the value can be anything, if we want to supply some details to the user then we need to add an object here otherwise we can add simple type like true here.

  1. if ((control.value as string).indexOf(' ') >= 0) {  
  2.     return { cannotContainSpace: true };  
  3. }  

And as for another example, if we’re going to build the built-in minLength validator, our object here will be like this,

  1. if ((control.value as string).indexOf(' ') >= 0) {  
  2.     return { minlength: {  
  3.         requiredLength: 10,  
  4.         actualLength: control.value.length  
  5.     }};  
  6. }  

So if you want to provide details  to the client, you use a complex object as a value here. Now let’s undo this, and come back to the cannotContainSpace. So if the validation fails, we return the validationerrors object or null.

  1. export class UsernameValidators {  
  2.     cannotContainSpace(control: AbstractControl) : ValidationErrors | null {  
  3.         if ((control.value as string).indexOf(' ') >= 0) {  
  4.             return { cannotContainSpace: true };  
  5.         }  
  6.         return null;  
  7.     }  
  8. }  

One last step, in order to access this cannotContainSpace() method to the outside without having to create an instance of the class, we decorate this method with static.

  1. import { AbstractControl, ValidationErrors } from "@angular/forms";  
  2.   
  3. export class UsernameValidators {  
  4.     static cannotContainSpace(control: AbstractControl) : ValidationErrors | null {  
  5.         if ((control.value as string).indexOf(' ') >= 0) {  
  6.             return { cannotContainSpace: true };  
  7.         }  
  8.         return null;  
  9.     }  
  10. }  

So here we have customValidator, now we need to import it into our component. Now let’s consume it in the component.

  1. form = new FormGroup({  
  2.   username: new FormControl('', [  
  3.     Validators.required,  
  4.     Validators.minLength(3),  
  5.     UsernameValidators.cannotContainSpace  
  6.   ]),  
  7.   password: new FormControl('', Validators.required)  
  8. });  

Before going any further, we just emphasize that in this demo I put this custom validator inside signup-form folder because this is the only place we’re using this validator. In a large complex applications, chances are you might have different components that use the same validator. If that’s the case you need to put all these validators in a common place. For example, add a folder ‘common’ in ‘app’ folder and inside we can place validators.

validators

Now let’s define the validation message for this custom validator.

  1. <div *ngIf="username.touched && username.invalid" class="alert alert-danger">  
  2.     <div *ngIf="username.errors.required">Username is Required.</div>  
  3.     <div *ngIf="username.errors.minlength">Username should be minimum {{ username.errors.minlength.requiredLength }} characters.</div>  
  4.     <div *ngIf="username.errors.cannotContainSpace">Username cannot contain space.</div>  
  5. </div>  

Here cannotContainSpace is the key which we defined in the return statement here.

  1. if ((control.value as string).indexOf(' ') >= 0) {  
  2.     return { cannotContainSpace: true };  
  3. }  

Now let’s try this validation in the browser.

validation in the browser

Asynchronous Operations

Sometimes we need to call the server to validate a given value. For example here we want to call the server to see if this username is taken or not. So let’s go and implement the custom validator. So back in the UsernameValidators class and here we’ll add another static method.

  1. static shouldBeUnique(control: AbstractControl) : ValidationErrors | null {  
  2. }  

Now to implement this method, we don’t want to call to server yet because we’ve not covered the http services yet and how we connect through http services in Angular. So let’s make it simple and simulate a call to the server.

  1. static shouldBeUnique(control: AbstractControl) : ValidationErrors | null {  
  2.     if (control.value === 'usama')   
  3.         return { shouldBeUnique: true };  
  4.     return null;  
  5. }  

So the username is usama, we assume it is taken otherwise the username is unique. And it is really very simple implementation.

What should we do to simulate the call to the server? Well calling the server is what we classify as an Asynchronous Operation. What is an Asynchronous Operation? When we call the server there is going to be little bit of a delay in I/O operations. That delay may be half a second or of 3 seconds or it may be 20 seconds depending upon the connection speed. In situations like that the process that is executing our code doesn’t want to block while waiting for the result coming back from the server. Because if that process blocks, user can’t interact with the browser. So that process is going to call that server behind the scenes and when the result is ready, it's going to display to the user. So this is what we call asynchronous operation. Asynchronous means none blocking (multiple tasks at a time).

Calling the server is not the only asynchronous operation in Javascript. Another example of asynchronous operation is the timeout function that you have probably seen before.

  1. setTimeout(() => {  
  2.     console.log('ok');  
  3. }, 2000);  

We can use this to simulate the call to the server. setTimeout() has callback function inside and we know that we can use the arrow functions here if callback function has no parameters. We already discussed it in our Typescript article. Now let’s move the code inside the callback function.

  1. static shouldBeUnique(control: AbstractControl) : ValidationErrors | null {  
  2.     setTimeout(() => {  
  3.         if (control.value === 'usama')   
  4.             return { shouldBeUnique: true };  
  5.         return null;  
  6.     }, 2000);  
  7. }  

setTimeout() is an asynchronous function which we can simulate this to the call to the server. It will be executed after 2k milliseconds and we don’t want to be blocked while waiting for 2 seconds.

The issue we’ve here in this code, return statement inside setTimeout() is not returning to the shouldBeUnique() function. They are just returning from the callback function that we pass to the setTimeout() function. Here we’re just simply calling setTimeout() function. If you want to get rid off this compilation error, we need to return something on the same scope of setTimeout() function.

  1. static shouldBeUnique(control: AbstractControl) : ValidationErrors | null {  
  2.     setTimeout(() => {  
  3.         if (control.value === 'usama')   
  4.             return { shouldBeUnique: true };  
  5.         return null;  
  6.     }, 2000);  
  7.   
  8.     return null;  
  9. }  

Now the compilation error is gone. However we can’t use this validator function inside our component because this function will always return null which means no error. So the issue we’ve here we’re dealing with asynchronous operation. When we’re building validator, it involves asynchronous operation and we need to use a different signature for our validator function.

Asynchronous Validations

Back in our component, here we create the FormControl object.

  1. form = new FormGroup({  
  2.   username: new FormControl('', Validators.required),  
  3.   password: new FormControl('', Validators.required)  
  4. });  

So far we’ve seen these 2 parameters. The 1st one is the initial value and the 2nd one is more or more validator function. Now let’s take a look at the 3rd parameter. Here we’ve async validator,

Asynchronous Validations

And as you can see the type of the parameter is either AsyncValidatorFn or an array of AsyncValidatorFn. Let’s go back to Angular website and look at the declaration of this interface.

AsyncValidatorFn

Now look at the signature of this function. Just like before this validator function takes parameter of type AbstractControl but the return type is a little bit different. Previously the return type was ValdiationErrors object or null. Now it is the promise of ValidationErrors or Observable of ValidationErrors or null. So basically this function should either return a promise or observable.

What are these classes (promise, observable)

In javascript, we use promises and observables to work with asynchronous operations. And here we discuss the promise, we’ll see observable later. We want our validator function to return promise of validation errors or null. First of all, study Promise in Javascript to get an idea about Promise.

So now let’s go back and modify our validator function to return a promise.

  1. static shouldBeUnique(control: AbstractControl) : Promise<ValidationErrors | null> {  
  2.     return new Promise()  
  3.       
  4.     setTimeout(() => {  
  5.         if (control.value === 'usama')   
  6.             return { shouldBeUnique: true };  
  7.         return null;  
  8.     }, 2000);  
  9.     return null;  
  10. }  

Now when we create the Promise object, here is the screenshot of constructor.

Bootstrap constructor

Now the complex thing has started, it is quite difficult to understand this concept. It's too complicated. Let’s try to understand.

Constructor

The reason it looks complicated is because of error function syntax. And here we’ve 3 error functions and we’ll simplify them one by one.

Look at the first one here,

  1. (value?: T | PromiseLike<T>) => void  

This error function represents function with this parameter.

  1. (value? : T | PromiseLike<T>)  

Which is either a value which might be optional or another Promise. So this function takes parameter and returns void. And all this is the parameter of resolve parameter here. Let’s simplify this and get rid of this error function syntax. Just remember,

  1. resolve: (value?: T | PromiseLike<T>) => void  

resolve is a function that takes a value and returns void.

Now look at the 2nd error function,

  1. (reason? : any) => void  

This function takes parameter called reason which is optional and the type of this parameter is any. And as you can see this function returns void. And once again,

  1. reject: (reason? : any) => void  
reject: (reason? : any) => void

this function is the type of reject parameter here. So let’s simplify this once again, but keep in mind reject is a function that takes an optional parameter called reason and returns void.

Now we’ve another error function

  1. executor: (resolve, reject) => void  

the function that takes 2 parameters (resolve and reject). And both these parameters are functions. So this function takes these 2 parameters and returns void. And this function is the type of executor which is the parameter of constructor of the Promise class.

Now back again in code and here we need to apply 2 parameters resolve and reject

  1. return new Promise((resolve, reject))  

One important thing is when you’re using resolve and reject here. Don’t import any statement. We’ve only 1 import statement here,

  1. import { AbstractControl, ValidationErrors } from "@angular/forms";  

So be aware. Otherwise you’ll see the error while using resolve and reject in your coode.

Now we know that both these parameters are functions. So this goes to code block. And this arrow function is the void function, we already know. We use resolve parameter to return a value with the consumer of this Promise so whoever is interested in this Promise when our asynchronous function completes, we want to give them some value. We use this resolve function to return a value. So instead of return true, we call resolve(true)

  1. static shouldBeUnique(control: AbstractControl) : Promise<ValidationErrors | null> {  
  2.     return new Promise((resolve, reject) => {  
  3.         resolve(true);  
  4.     });  
  5.   
  6.     setTimeout(() => {  
  7.         if (control.value === 'usama')   
  8.             return { shouldBeUnique: true };  
  9.         return null;  
  10.     }, 2000);  
  11.       
  12.     return null;  
  13. }  

And in case of failure, we’re gonna use reject parameter. And in reject, we can optionally specify the reason of failure.

  1. return new Promise((resolve, reject) => {  
  2.     reject('error');  
  3. });  

And if it is the complex object with details then we can create the object inside reject().

So now that we have the promise object and now we move the setTimeout() code into the body of executor function.

  1. static shouldBeUnique(control: AbstractControl) : Promise<ValidationErrors | null> {  
  2.     return new Promise((resolve, reject) => {  
  3.         setTimeout(() => {  
  4.             if (control.value === 'usama')   
  5.                 return { shouldBeUnique: true };  
  6.             return null;  
  7.         }, 2000);  
  8.     });  
  9.       
  10.    // Remember: it is for demo purpose, it is actually not part of real implementation.  
  11.     return null;  
  12. }  

So now here we have the asynchronous operation when this operation completes, you want to return the value, the consumer of this promise. So instead of returning ValidationErrors object here, we're going to call resolve and give the object into it.

  1. if (control.value === 'usama')   
  2.     resolve({ shouldBeUnique: true });  

And similarly instead of returning null, we use resolve(null)

  1. static shouldBeUnique(control: AbstractControl) : Promise<ValidationErrors | null> {  
  2.     return new Promise((resolve, reject) => {  
  3.         setTimeout(() => {  
  4.             if (control.value === 'usama')   
  5.                 resolve({ shouldBeUnique: true });  
  6.             else  
  7.                 resolve(null);  
  8.         }, 2000);  
  9.     });  
  10. }  

So this is how we implement an async validator. We can use these parameters (resolve and reject) and either return some value to the consumer of this promise or return an error. Now finally we need to register this in our FormControl object,

  1. export class SignupFormComponent {  
  2.   form = new FormGroup({  
  3.     username: new FormControl('',   
  4.       Validators.required,   
  5.       UsernameValidators.shouldBeUnique),  
  6.     password: new FormControl('', Validators.required)  
  7.   });  
  8.   
  9.   get username() {  
  10.     return this.form.get('username');  
  11.   }  
  12. }  

Now to display this validation error, we should go to our template and add one more div with validation error message.

  1. <div *ngIf="username.touched && username.invalid" class="alert alert-danger">  
  2.     <div *ngIf="username.errors.required">Username is Required.</div>  
  3.     <div *ngIf="username.errors.minlength">Username should be minimum {{ username.errors.minlength.requiredLength }} characters.</div>  
  4.     <div *ngIf="username.errors.cannotContainSpace">Username cannot contain space.</div>  
  5.     <div *ngIf="username.errors.shouldBeUnique">Username is already taken.</div>  
  6. </div>  

Now let’s test this,

 error message

We can see the error message is showing but it will show on the screen after 2 seconds because we’ve a delay in setTimeout() of 2s. And our browser is not blocking, it is working in the delay as well.

Showing a Loader Image

When working with async validator you may want to display the loader image while the asynchronous operation is in progress. Implementation is very easy, let’s see how can we implement this feature.

Let’s add this div containing message just below the input username

  1. <div class="form-group">  
  2.         <label for="username">Username</label>  
  3.         <input   
  4.             formControlName="username"  
  5.             id="username"   
  6.             type="text"   
  7.             class="form-control">  
  8.         <div>Checking for Uniqueness...</div>  
  9.         <div *ngIf="username.touched && username.invalid" class="alert alert-danger">  
  10.             <div *ngIf="username.errors.required">Username is Required.</div>  
  11.             <div *ngIf="username.errors.minlength">Username should be minimum {{ username.errors.minlength.requiredLength }} characters.</div>  
  12.             <div *ngIf="username.errors.cannotContainSpace">Username cannot contain space.</div>  
  13.             <div *ngIf="username.errors.shouldBeUnique">Username is already taken.</div>  
  14.         </div>  
  15.     </div>  

So we wanto to display this div while asynchronous is waiting for the result to come back from the server. But how do we know if our asynchronous validator is in progress? We know that

  1. formControlName="username"  

this username is a property in our component and references to FormControl object. Our FormControl has a property called pending. Pending returns true if at least one async validator is in progress. So,

  1. <div *ngIf="username.pending">Checking for Uniqueness...</div>  

Bootstrap Form
Validating the Form on Submit

While async validators are useful for validating input fields sometimes we need to do validation upon submitting the forms to the server. Imagine instead of a sign up form we’re building login form. In this case, we need to submit both the username and password to the server in order to see if this is a valid login or not. We can just validate the username and password. So let’s see how to add validation when submitting the form.

So back in component, we add ngSubmit directive and define the function in the component. First in the top here we handle ngSubmit event. Once again to referesh your memory, we’ve ngForm that is apply to all form elements by default. And this event exposes on output property or an event called ngSubmit. So we can use it in event binding expression.

  1. <form [formGroup]="form" (ngSubmit)="login()">  
  2. </form>  

Now back in our component. Here we're going to call the server to see if username and password is valid or not. Once again, we simplify things. So now don’t worry about calling the server but in the future we're going to have a separate class called service which talks to the server. Let’s imagine,

  1. login() {  
  2.   let isValid = authService.login(this.form.value);  
  3.   if (!isValid) {  
  4.     this.form.setErrors({  
  5.       invalidLogin: true  
  6.     });  
  7.   }  
  8. }  

The name of that service class is authService and this has method login. Here we need to pass json object in the login method. So in this json object we’ve 2 properties (username and password). Now when we call this login method eventually we get the value from server, most likely boolean that determines if the username and password are valid or not. So if isValid is false, now here we want to add the validation errors to the form. Here in SignupComponent we have FormGroup class with form instance. As we know that FormGroup and FormControl classes derive from AbstractControl which defines the common properties and methods. And one of these methods is setErrors. And we’re using setErrors at form level. If you want to set setErrors at individual control level, we should call this method on a particular control object i.e.

  1. this.username.setErrors  

But here we set the validation errors at form level. And in setErrors, here we add validation errors object just like when we implemented custom validator. So

  1. this.form.setErrors({  
  2.   invalidLogin: true  
  3. });  

Now let’s revert back to make it work, this was just for the demo purposes. Because here we don't have any service. So, the final code is

  1. login() {  
  2.   this.form.setErrors({  
  3.     invalidLogin: true  
  4.   });  
  5. }  

So basically whenever we submit the form, we're going to get this error in any condition. Now back in the template place this div is just below the form html element, above all the form-group divs

  1. <div class="alert-alert-danger"></div>  

Now we want to add this only if our form has errors. So once again here we use ngIf

  1. <div *ngIf="form.errors" class="alert-alert-danger">  
  2.     The username or password is invalid.  
  3. </div>  

Now let’s test the application in the browser. Initially we don’t have any errors. But when we click this even without filling the form, we get this validation error.

validation error

So here is our final code for component.ts and its template,

  1. import { Component } from '@angular/core';  
  2. import { FormGroup, FormControl, Validators } from "@angular/forms";  
  3. import { UsernameValidators } from './username.validators';  
  4.   
  5. @Component({  
  6.   selector: 'signup-form',  
  7.   templateUrl: './signup-form.component.html',  
  8.   styleUrls: ['./signup-form.component.css']  
  9. })  
  10. export class SignupFormComponent {  
  11.   form = new FormGroup({  
  12.     username: new FormControl('',   
  13.       Validators.required,   
  14.       UsernameValidators.shouldBeUnique),  
  15.     password: new FormControl('', Validators.required)  
  16.   });  
  17.   
  18.   login() {  
  19.     this.form.setErrors({  
  20.       invalidLogin: true  
  21.     });  
  22.   }  
  23.   
  24.   get username() {  
  25.     return this.form.get('username');  
  26.   }  
  27. }  

Signup.component.html code is

  1. <form [formGroup]="form" (ngSubmit)="login()">  
  2.     <div *ngIf="form.errors" class="alert alert-danger">  
  3.         The username or password is invalid.  
  4.     </div>  
  5.     <div class="form-group">  
  6.         <label for="username">Username</label>  
  7.         <input   
  8.             formControlName="username"  
  9.             id="username"   
  10.             type="text"   
  11.             class="form-control">  
  12.         <div *ngIf="username.pending">Checking for Uniqueness...</div>  
  13.         <div *ngIf="username.touched && username.invalid" class="alert alert-danger">  
  14.             <div *ngIf="username.errors.required">Username is Required.</div>  
  15.             <div *ngIf="username.errors.minlength">Username should be minimum {{ username.errors.minlength.requiredLength }} characters.</div>  
  16.             <div *ngIf="username.errors.cannotContainSpace">Username cannot contain space.</div>  
  17.             <div *ngIf="username.errors.shouldBeUnique">Username is already taken.</div>  
  18.         </div>  
  19.     </div>  
  20.     <div class="form-group">  
  21.         <label for="password">Password</label>  
  22.         <input   
  23.             formControlName="password"  
  24.             id="password"   
  25.             type="text"   
  26.             class="form-control">  
  27.     </div>  
  28.     <button class="btn btn-primary" type="submit">Sign Up</button>  
  29. </form>  

Nested FormGroups

So this form we have here is a simple form. We’ve one group with 2 controls inside group.

  1. export class SignupFormComponent {  
  2.   form = new FormGroup({  
  3.     username: new FormControl(''),  
  4.     password: new FormControl('')  
  5.   });  
  6.   
  7.   get username() {  
  8.     return this.form.get('username');  
  9.   }  
  10. }  

In a large complex applications, you might have a form with multiple subgroups. So let’s see how to implement that. Imagine this username and password controls are part of the larger form. And in that form, we’re going to have a subgroup called account. So here in FormGroup object, we add the key called account and instead of creating a new FormControl, here we create FormGroup with an object and inside this group we’ve properties. And also you need to update your username() method as well where you should have fully qualified path of username.

  1. export class SignupFormComponent {  
  2.   form = new FormGroup({  
  3.     account: new FormGroup({  
  4.       username: new FormControl(''),  
  5.       password: new FormControl('')  
  6.     })  
  7.   });  
  8.   
  9.   get username() {  
  10.     return this.form.get('account.username');  
  11.   }  
  12. }  

Now let’s go back to our template, and here our html template is

  1. <form [formGroup]="form">  
  2.     <div class="form-group">  
  3.         <label for="username">Username</label>  
  4.         <input   
  5.             formControlName="username"  
  6.             id="username"   
  7.             type="text"   
  8.             class="form-control">  
  9.         <div *ngIf="username.pending">Checking for Uniqueness...</div>  
  10.         <div *ngIf="username.touched && username.invalid" class="alert alert-danger">  
  11.             <div *ngIf="username.errors.required">Username is Required.</div>  
  12.             <div *ngIf="username.errors.minlength">Username should be minimum {{ username.errors.minlength.requiredLength }} characters.</div>  
  13.             <div *ngIf="username.errors.cannotContainSpace">Username cannot contain space.</div>  
  14.             <div *ngIf="username.errors.shouldBeUnique">Username is already taken.</div>  
  15.         </div>  
  16.     </div>  
  17.     <div class="form-group">  
  18.         <label for="password">Password</label>  
  19.         <input   
  20.             formControlName="password"  
  21.             id="password"   
  22.             type="text"   
  23.             class="form-control">  
  24.     </div>  
  25.     <button class="btn btn-primary" type="submit">Sign Up</button>  
  26. </form>  

Note that we’re using formControlName=”username” associated the input field with the FormControl object that we created in our component. And if we go to the browser, here we’ll see the error,

error

It is because username is no longer at root control. Now it is the part of Group called account. So in Angular, as we have the directive called formControlName we have another directive called formGroupName. So we create on more div and move username password divs inside that div.

  1. <form [formGroup]="form">  
  2.     <div formGroupName="account">  
  3.         <div class="form-group">  
  4.             <label for="username">Username</label>  
  5.             <input   
  6.                 formControlName="username"  
  7.                 id="username"   
  8.                 type="text"   
  9.                 class="form-control">  
  10.             <div *ngIf="username.pending">Checking for Uniqueness...</div>  
  11.             <div *ngIf="username.touched && username.invalid" class="alert alert-danger">  
  12.                 <div *ngIf="username.errors.required">Username is Required.</div>  
  13.                 <div *ngIf="username.errors.minlength">Username should be minimum {{ username.errors.minlength.requiredLength }} characters.</div>  
  14.                 <div *ngIf="username.errors.cannotContainSpace">Username cannot contain space.</div>  
  15.                 <div *ngIf="username.errors.shouldBeUnique">Username is already taken.</div>  
  16.             </div>  
  17.         </div>  
  18.         <div class="form-group">  
  19.             <label for="password">Password</label>  
  20.             <input   
  21.                 formControlName="password"  
  22.                 id="password"   
  23.                 type="text"   
  24.                 class="form-control">  
  25.         </div>  
  26.     </div>  
  27.     <button class="btn btn-primary" type="submit">Sign Up</button>  
  28. </form>  

Now when angular access the username FormControl. He’ll automatically get an idea it is lying under formGroup account. And now if we go back to the browser, the error is gone. This is how we use nested group in the form.

FormArray

Sometimes we need to work with array of objects in a form. Let’s see something new with some practical.

First of all make a new component NewCourseFormComponent

PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g c NewCourseForm

  1. import { Component } from '@angular/core';  
  2. import { FormGroup, FormControl, FormArray } from '@angular/forms';  
  3.   
  4. @Component({  
  5.   selector: 'app-new-course-form',  
  6.   templateUrl: './new-course-form.component.html',  
  7.   styleUrls: ['./new-course-form.component.css']  
  8. })  
  9. export class NewCourseFormComponent {  
  10.   form = new FormGroup({  
  11.     topics: new FormArray([])  
  12.   });  
  13. }  

When we’re dealing with an array of objects, instead of FormControl we use FormArray class. The first argument of FormArray is the elements which we’ll add dynamically here. Now let’s go back to our template

  1. <form>  
  2.   <input   
  3.     type="text" class="form-control"  
  4.     (keyup.enter)="addTopic(topic)" #topic>  
  5. </form>  

Here we’re passing reference to this input field as the method argument. Now let’s define the addTopic() method.

  1. export class NewCourseFormComponent {  
  2.   form = new FormGroup({  
  3.     topics: new FormArray([])  
  4.   });  
  5.   
  6.   addTopic(topic: HTMLInputElement) {  
  7.   
  8.   }  
  9. }  

Here we’ve applied type annotation because somebody is reading code. They may think topic is the json object and represents actual topic. But it is actually Html input element so here we use HTMLInputElement

Now we need to get the reference of topics property which is a FormArray. Once we get this then we can push a new value into this array. So what we do,

  1. addTopic(topic: HTMLInputElement) {  
  2.   this.form.get('topics')  

Now this get() method returns an AbstractControl object and AbstractControl doesn’t have push method because it is purely defined in FormArray class. So we need to explicitly cast this with FormArray

  1. addTopic(topic: HTMLInputElement) {  
  2.   (this.form.get('topics') as FormArray).push()  
  3. }  

Now push method expects AbstractControl. Now once again, just to refresh the memory AbstractControl is the base class of FormControl, FormGroup and FormArray.

So all these classes are essentially AbstractControl and we can use them here in push arguments.

AbstractControl

Now in this application, each topic is a simple value. So we’re going to pass FormControl object here in push() method. In your application, each item may be complex object then you would add FormGroup object here.

add FormGroup object

Now let’s initialize it with the value we have in input field.

  1. export class NewCourseFormComponent {  
  2.   form = new FormGroup({  
  3.     topics: new FormArray([])  
  4.   });  
  5.   
  6.   addTopic(topic: HTMLInputElement) {  
  7.     (this.form.get('topics') as FormArray).push(new FormControl(topic.value));  
  8.     topic.value = '';  
  9.   }  
  10. }  

topic.value = ‘’ is used for to clear the input textbox on first time enter hit.

Now we implemented addTopic() method. Now let’s go back to our template and write some markup to render all the topics

  1. <form>  
  2.   <input   
  3.     type="text" class="form-control"  
  4.     (keyup.enter)="addTopic(topic)" #topic>  
  5.   <ul class="list-group">  
  6.     <li  
  7.       *ngFor="let topic of form.get('topics').controls"   
  8.       class="list-group-item">  
  9.       {{ topic.value }}  
  10.     </li>  
  11.   </ul>  
  12. </form>  

form.get(‘topic’) returns FormArray object and here we have a property called controls. So this way we can iterate over all the FormControl objects inside this array. Now let’s test the application up to this point

test the application

Now before going any further, let’s refactor the code little bit.

  1. import { Component } from '@angular/core';  
  2. import { FormGroup, FormControl, FormArray } from '@angular/forms';  
  3.   
  4. @Component({  
  5.   selector: 'app-new-course-form',  
  6.   templateUrl: './new-course-form.component.html',  
  7.   styleUrls: ['./new-course-form.component.css']  
  8. })  
  9. export class NewCourseFormComponent {  
  10.   form = new FormGroup({  
  11.     topics: new FormArray([])  
  12.   });  
  13.   
  14.   addTopic(topic: HTMLInputElement) {  
  15.     this.topics.push(new FormControl(topic.value));  
  16.     topic.value = '';  
  17.   }  
  18.   
  19.   get topics() {  
  20.     return this.form.get('topics') as FormArray;  
  21.   }  
  22. }  

Similarly go back to the template and in the loop, refactor the code.

  1. <ul class="list-group">  
  2.   <li  
  3.     *ngFor="let topic of topics.controls"   
  4.     class="list-group-item">  
  5.     {{ topic.value }}  
  6.   </li>  
  7. </ul>  

Now let’s implement the remove functionality,

  1. <ul class="list-group">  
  2.   <li  
  3.     *ngFor="let topic of topics.controls"   
  4.     (click)="removeTopic(topic)"  
  5.     class="list-group-item">  
  6.     {{ topic.value }}  
  7.   </li>  
  8. </ul>  

We’re passing topic instance of FormControl to the removeTopic as an argument. Now back in our component.

  1. export class NewCourseFormComponent {  
  2.   form = new FormGroup({  
  3.     topics: new FormArray([])  
  4.   });  
  5.   
  6.   addTopic(topic: HTMLInputElement) {  
  7.     this.topics.push(new FormControl(topic.value));  
  8.     topic.value = '';  
  9.   }  
  10.   
  11.   removeTopic(topic: FormControl) {  
  12.     let index = this.topics.controls.indexOf(topic);  
  13.     this.topics.removeAt(index);  
  14.   }  
  15.   
  16.   get topics() {  
  17.     return this.form.get('topics') as FormArray;  
  18.   }  
  19. }  

Now let’s test the application, and you’ll see it is working on clicking the items they are removing automatically.

FormBuilder

Now we’ve simplified the code. And removed all the additional methods that we don’t need here. So here we have form object.

  1. export class NewCourseFormComponent {  
  2.   form = new FormGroup({  
  3.     topics: new FormArray([])  
  4.   });  
  5. }  

Now let’s add one more property name of FormControl. So now we have all the directives of AbstractControl class

  1. export class NewCourseFormComponent {  
  2.   form = new FormGroup({  
  3.     name: new FormControl(),  
  4.     topics: new FormArray([])  
  5.   });  
  6. }  

And to make it more comprehensive, we add one more somegroup inside form.

  1. export class NewCourseFormComponent {  
  2.   form = new FormGroup({  
  3.     name: new FormControl(),  
  4.     contact: new FormGroup({  
  5.       email: new FormControl(),  
  6.       phone: new FormControl()  
  7.     }),  
  8.     topics: new FormArray([])  
  9.   });  
  10. }  

Now there is a cleaner way to write the above same code, here we’ve FormBuilder class in Angular for building forms. So first we add constructor and inject FormBuilder object here. And we’ve 3 methods with instance of FormBuilder.

FormBuilder

Now as we have defined the form with FormGroup, let’s try to rewrite the code with FormBuilder.

  1. constructor(fb: FormBuilder){  
  2.   fb.group({  
  3.     name: fb.control()  
  4.   });  
  5. }  

But fb.control is not the cleaner way to implement. So when we want to create control, we need to pass an array and the elements we mention here will be used as the arguments to the FormControl object. So let’s say here we have 2 arguments.

name: new FormControl(‘’, Validators.required)

Now to rewrite the same code using FormBuilder in this array

  1. export class NewCourseFormComponent {  
  2.   form = new FormGroup({  
  3.     name: new FormControl('', Validators.required),  
  4.     contact: new FormGroup({  
  5.       email: new FormControl(),  
  6.       phone: new FormControl()  
  7.     }),  
  8.     topics: new FormArray([])  
  9.   });  
  10.   
  11.   constructor(fb: FormBuilder){  
  12.     fb.group({  
  13.       name: ['', Validators.required]  
  14.     });  
  15.   }  
  16. }  

This syntax is little bit shorter,

  1. export class NewCourseFormComponent {  
  2.   form = new FormGroup({  
  3.     name: new FormControl('', Validators.required),  
  4.     contact: new FormGroup({  
  5.       email: new FormControl(),  
  6.       phone: new FormControl()  
  7.     }),  
  8.     topics: new FormArray([])  
  9.   });  
  10.   
  11.   constructor(fb: FormBuilder){  
  12.     fb.group({  
  13.       name: ['', Validators.required],  
  14.       contact: fb.group({  
  15.         email: [],  
  16.         phone: []  
  17.       }),  
  18.       topics: fb.array([])  
  19.     });  
  20.   }  
  21. }  

Now compare both approaches and we’ll get to the point that the 2nd approach is much cleaner. But it is completely on your personal preference. One more thing, FormBuilder object is not massively used in our practical life. We just find it in online tutorials. So finally, we need to get the result and store it in our form field. So,

  1. export class NewCourseFormComponent {  
  2.   form;  
  3.   
  4.   constructor(fb: FormBuilder){  
  5.     this.form = fb.group({  
  6.       name: ['', Validators.required],  
  7.       contact: fb.group({  
  8.         email: [],  
  9.         phone: []  
  10.       }),  
  11.       topics: fb.array([])  
  12.     });  
  13.   }  
  14. }  

So this is how we use FormBuilder class.

Recap The Things

So with reactive forms we build our form explicitly in the code inside our component. And now our template code becomes also very simple.

  1. <form [formGroup]="form">  
  2.   <input formControlName="name">  
  3.   <div formGroupName="contact">  
  4.     <input formControlName="email">  
  5.     <input formControlName="phone">  
  6.   </div>  
  7.   <!-- if we have formArray in our component -->  
  8.   <ul>  
  9.     <li *ngFor="let topic of topics.controls">  
  10.       {{ topic.value }}  
  11.     </li>  
  12.   </ul>  
  13. </form>  

We use  a few directives here to associate the controls we created in component to the elements of this form. There are 3 directives we need to remember in Reactive Forms.

  • formGroup
  • formControlName
  • formGroupName

What We Have Learned

Now let’s we make the confirm password form. So create a new component.

PS > ng g c changepassword

And here we make the form elements in code.

  1. export class ChangepasswordComponent {  
  2.   form: FormGroup;  
  3.   
  4.   constructor(fb: FormBuilder) {  
  5.     this.form = fb.group({  
  6.       oldPassword: ['', Validators.required],  
  7.       newPassword: ['', Validators.required],  
  8.       confirmPassword: ['', Validators.required]  
  9.     });  
  10.   }  
  11. }  

Now back to component template. Here we create the form.

  1. <form [formGroup]="form">    
  2. </form>  

Now with the help of Zencoding, we create our html elements.

(div.form-group>label+input[type='password'].form-control)*3+button.btn.btn-primary Press TAB

And it will generate the code, something like that.

  1. <form [formGroup]="form">  
  2.   <div class="form-group">  
  3.     <label for=""></label>  
  4.     <input type="password" class="form-control">  
  5.   </div>  
  6.   <div class="form-group">  
  7.     <label for=""></label>  
  8.     <input type="password" class="form-control">  
  9.   </div>  
  10.   <div class="form-group">  
  11.     <label for=""></label>  
  12.     <input type="password" class="form-control">  
  13.   </div>  
  14.   <button class="btn btn-primary"></button>  
  15. </form>  

Now let’s add the labels here in the form and now we need to associate each input field with the FormControl object that we created in our component.

  1. <form [formGroup]="form">  
  2.   <div class="form-group">  
  3.     <label for="">Old Password</label>  
  4.     <input formControlName="oldPassword" type="password" class="form-control">  
  5.   </div>  
  6.   <div class="form-group">  
  7.     <label for="">New Password</label>  
  8.     <input formControlName="newPassword" type="password" class="form-control">  
  9.   </div>  
  10.   <div class="form-group">  
  11.     <label for="">Confirm Password</label>  
  12.     <input formControlName="confirmPassword" type="password" class="form-control">  
  13.   </div>  
  14.   <button class="btn btn-primary">Change Password</button>  
  15. </form>  

Now our html form is ready. And if we focus out from the input textbox, it will become red bordered which means required validation is working.

 input textbox

Now we need to add Validation Errors. So back in our templates after each input field we’ll create the div for validation error messages.

  1. <div class="form-group">  
  2.   <label for="">Old Password</label>  
  3.   <input formControlName="oldPassword" type="password" class="form-control">  
  4.   <div   
  5.     *ngIf="form.get('oldPassword')"  
  6.     class="alert alert-danger">  
  7.   </div>  
  8. </div>  

This is how we access the oldPassword form control in template. But it is lengthy and if we use oldPassword control at multiple places then we need to write it again and again in all the places. So let’s move it in the property of component.

  1. export class ChangepasswordComponent {  
  2.   form: FormGroup;  
  3.   
  4.   constructor(fb: FormBuilder) {  
  5.     this.form = fb.group({  
  6.       oldPassword: ['', Validators.required],  
  7.       newPassword: ['', Validators.required],  
  8.       confirmPassword: ['', Validators.required]  
  9.     });  
  10.   }  
  11.   
  12.   get oldPassword() { return this.form.get('oldPassword'); }  
  13.   get newPassword() { return this.form.get('newPassword'); }  
  14.   get confirmPassword() { return this.form.get('confirmPassword'); }  
  15. }  

Now back in the template,

  1. <div   
  2.   *ngIf="oldPassword.touched && oldPassword.invalid"  
  3.   class="alert alert-danger">  
  4.   Old Password Is Required.  
  5. </div>  

Actually we’ve 2 types of validation errors. One is for required and other is for asynchronous validator. Asynchronous validators are the ones we have not implemented yet. So here we’ve multiple validation messages. So, we create a separate div for each message.

  1. <form [formGroup]="form">  
  2.   <div class="form-group">  
  3.     <label for="">Old Password</label>  
  4.     <input formControlName="oldPassword" type="password" class="form-control">  
  5.     <div   
  6.       *ngIf="oldPassword.touched && oldPassword.invalid"  
  7.       class="alert alert-danger">  
  8.       <div *ngIf="oldPassword.errors.required">Old Password Is Required.</div>  
  9.     </div>  
  10.   </div>  
  11.   <div class="form-group">  
  12.     <label for="">New Password</label>  
  13.     <input formControlName="newPassword" type="password" class="form-control">  
  14.     <div   
  15.       *ngIf="newPassword.touched && newPassword.invalid"  
  16.       class="alert alert-danger">  
  17.       <div *ngIf="newPassword.errors.required">New Password Is Required.</div>  
  18.     </div>  
  19.   </div>  
  20.   <div class="form-group">  
  21.     <label for="">Confirm Password</label>  
  22.     <input formControlName="confirmPassword" type="password" class="form-control">  
  23.     <div   
  24.       *ngIf="confirmPassword.touched && confirmPassword.invalid"  
  25.       class="alert alert-danger">  
  26.       <div *ngIf="confirmPassword.errors.required">Confirm Password Is Required.</div>  
  27.     </div>  
  28.   </div>  
  29.   <button class="btn btn-primary">Change Password</button>  
  30. </form>  

Now let’s test the application up to this point.

async validator for the old password

Now let’s implement async validator for the old password. So in changepassword folder, create a new file (password.validators.ts)

  1. import { AbstractControl } from '@angular/forms';  
  2.   
  3. export class PasswordValidators {  
  4.     static validOldPassword(control: AbstractControl) {  
  5.           
  6.     }  
  7. }  

You might be thinking that we’re not explicitly mentioning the Promise return type here. Actually it is not necessary right now, because we don’t really need it.

  1. export class PasswordValidators {  
  2.     static validOldPassword(control: AbstractControl) {  
  3.         return new Promise((resolve) => {  
  4.             if (control.value !== '1234')   
  5.                 resolve({ invalidOldPassword: true});  
  6.             resolve(null);  
  7.         });  
  8.     }  
  9. }  

Now let’s consume this validOldPassword method in our changepassword component.

  1. constructor(fb: FormBuilder) {  
  2.   this.form = fb.group({  
  3.     oldPassword: ['',   
  4.       Validators.required,  
  5.       PasswordValidators.validOldPassword  
  6.     ],  
  7.     newPassword: ['', Validators.required],  
  8.     confirmPassword: ['', Validators.required]  
  9.   });  
  10. }  

Now let’s define the error message in the template.

  1. <div class="form-group">  
  2.   <label for="">Old Password</label>  
  3.   <input formControlName="oldPassword" type="password" class="form-control">  
  4.   <div   
  5.     *ngIf="oldPassword.touched && oldPassword.invalid"  
  6.     class="alert alert-danger">  
  7.     <div *ngIf="oldPassword.errors.required">Old Password Is Required.</div>  
  8.     <div *ngIf="oldPassword.errors.invalidOldPassword">  
  9.       Old Password Is Invalid.  
  10.     </div>  
  11.   </div>  
  12. </div>  

Now let’s test the application

test the application

And look it is working. Only 1234 old Password is allowed here.

Now the last point, we want to implement confirm password validation. Now with confirm password, we need to know this form as a whole because we need to access 2 controls (new Password and confirm Password fields). So back to our Password validator, I’m gonna define another validator.

  1. static passwordsShouldMatch(control: AbstractControl) {  
  2. }  

And this AbstractControl should be FormGroup. In other words, we wanna apply this validator at form level and you know the form is an instance of FormGroup class and because FormGroup derives from AbstractControl. So it can be passed to an Angular at runtime to this method. So here we need to get the reference of new password and confirm password fields. So the complete logic of the 2nd function and the whole file is,

  1. import { AbstractControl } from '@angular/forms';  
  2.   
  3. export class PasswordValidators {  
  4.     static validOldPassword(control: AbstractControl) {  
  5.         return new Promise((resolve) => {  
  6.             if (control.value !== '1234')   
  7.                 resolve({ invalidOldPassword: true});  
  8.   
  9.             resolve(null);  
  10.         });  
  11.     }  
  12.   
  13.     static passwordsShouldMatch(control: AbstractControl) {  
  14.         let newPassword = control.get('newPassword');  
  15.         let confirmPassword = control.get('confirmPassword');  
  16.   
  17.         if(newPassword.value !== confirmPassword.value)  
  18.             return { passwordsShouldMatch: true };  
  19.               
  20.         return null;  
  21.     }  
  22. }  

So back to our component, just like we can apply validators to each control, we can also apply them with group. So here

fb.group()

this group method of FormBuilder class takes two object parameters. The 1st parameter is where we define our controls and the 2nd one is another object where we add extra stuff to this FormGroup. So if we see the intellisense of group() we’ll get to know how can we implement this function.

  1. constructor(fb: FormBuilder) {  
  2.   this.form = fb.group({  
  3.     oldPassword: ['',   
  4.       Validators.required,  
  5.       PasswordValidators.validOldPassword  
  6.     ],  
  7.     newPassword: ['', Validators.required],  
  8.     confirmPassword: ['', Validators.required]  
  9.   }, {  
  10.     validator: PasswordValidators.passwordsShouldMatch  
  11.   });  
  12. }  

Validator is the key here. Now finally let’s go back to our template and add the Error Message by adding div.

  1. <div class="alert alert-danger"></div>  

And we want to render this only if the passwords don’t match. So we can get the reference of form here.

  1. <div *ngIf="form.invalid && form.errors.passwordsShouldMatch" class="alert alert-danger">  
  2.   Password Don't Match.  
  3. </div>  

As we’re implementing the validation messages before as well so we always use formcontrol property with validation messages, but here we’re accessing the complete form and checking the state of its html elements by implementing the function. So we’re using form.errors.passwordsShouldMatch, so don’t be confused. And one more check we need to implement here is if confirmPassword property has the value it means if it is valid then show the validation message.

  1. <div *ngIf="confirmPassword.valid && form.invalid && form.errors.passwordsShouldMatch" class="alert alert-danger">  
  2.   Password Don't Match.  
  3. </div>  

Now run the application and see the results.

Reactive Forms


Similar Articles